diff options
author | Andrejs Verza <andrejs.verza@zabbix.com> | 2022-11-04 19:03:36 +0300 |
---|---|---|
committer | Andrejs Verza <andrejs.verza@zabbix.com> | 2022-11-04 19:03:36 +0300 |
commit | 8728520cd26b9f9bce8631e8006d8e2bb8321f47 (patch) | |
tree | 6154203eb4c2bc6f9fe53129c7dce97e7f65ef33 | |
parent | 36c0a0d2147b692645e389d8145d5677c72b657a (diff) |
..F....... [ZBXNEXT-7469] implemented dashboard configuration monitoring
-rw-r--r-- | ui/app/controllers/CControllerDashboardConfigurationHashGet.php | 84 | ||||
-rw-r--r-- | ui/app/controllers/CControllerDashboardView.php | 10 | ||||
-rw-r--r-- | ui/app/views/js/monitoring.dashboard.view.js.php | 7 | ||||
-rw-r--r-- | ui/app/views/monitoring.dashboard.view.php | 1 | ||||
-rw-r--r-- | ui/include/classes/helpers/CDashboardHelper.php | 12 | ||||
-rw-r--r-- | ui/include/classes/mvc/CRouter.php | 1 | ||||
-rw-r--r-- | ui/js/class.dashboard.js | 125 |
7 files changed, 234 insertions, 6 deletions
diff --git a/ui/app/controllers/CControllerDashboardConfigurationHashGet.php b/ui/app/controllers/CControllerDashboardConfigurationHashGet.php new file mode 100644 index 00000000000..93b24950ba3 --- /dev/null +++ b/ui/app/controllers/CControllerDashboardConfigurationHashGet.php @@ -0,0 +1,84 @@ +<?php declare(strict_types = 0); +/* +** Zabbix +** Copyright (C) 2001-2022 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + + +class CControllerDashboardConfigurationHashGet extends CController { + + private ?array $db_dashboard = null; + + protected function init(): void { + $this->setPostContentType(self::POST_CONTENT_TYPE_JSON); + } + + /** + * @throws JsonException + */ + protected function checkInput(): bool { + $fields = [ + 'dashboardid' => 'required|db dashboard.dashboardid|not_empty' + ]; + + $ret = $this->validateInput($fields); + + if (!$ret) { + $this->setResponse( + new CControllerResponseData(['main_block' => json_encode([ + 'error' => [ + 'messages' => array_column(get_and_clear_messages(), 'message') + ] + ], JSON_THROW_ON_ERROR)]) + ); + } + + return $ret; + } + + protected function checkPermissions(): bool { + if (!$this->checkAccess(CRoleHelper::UI_MONITORING_DASHBOARD)) { + return false; + } + + $db_dashboards = API::Dashboard()->get([ + 'output' => ['name', 'display_period', 'auto_start'], + 'selectPages' => ['dashboard_pageid', 'name', 'display_period', 'widgets'], + 'dashboardids' => $this->getInput('dashboardid') + ]); + + if (!$db_dashboards) { + return false; + } + + $this->db_dashboard = $db_dashboards[0]; + + return true; + } + + protected function doAction(): void { + $this->db_dashboard['pages'] = CDashboardHelper::preparePagesForGrid($this->db_dashboard['pages'], null, true); + + $widget_defaults = APP::ModuleManager()->getWidgetsDefaults(); + + $output = [ + 'configuration_hash' => CDashboardHelper::getConfigurationHash($this->db_dashboard, $widget_defaults) + ]; + + $this->setResponse(new CControllerResponseData(['main_block' => json_encode($output, JSON_THROW_ON_ERROR)])); + } +} diff --git a/ui/app/controllers/CControllerDashboardView.php b/ui/app/controllers/CControllerDashboardView.php index 3c17e9b9855..8ea10a11570 100644 --- a/ui/app/controllers/CControllerDashboardView.php +++ b/ui/app/controllers/CControllerDashboardView.php @@ -83,6 +83,9 @@ class CControllerDashboardView extends CController { return true; } + /** + * @throws JsonException + */ protected function doAction() { [$dashboard, $error] = $this->getDashboard(); @@ -120,10 +123,15 @@ class CControllerDashboardView extends CController { updateTimeSelectorPeriod($time_selector_options); + $widget_defaults = APP::ModuleManager()->getWidgetsDefaults(); + $data = [ 'dashboard' => $dashboard, - 'widget_defaults' => APP::ModuleManager()->getWidgetsDefaults(), + 'widget_defaults' => $widget_defaults, 'widget_last_type' => CDashboardHelper::getWidgetLastType(), + 'configuration_hash' => $dashboard['dashboardid'] !== null + ? CDashboardHelper::getConfigurationHash($dashboard, $widget_defaults) + : null, 'has_time_selector' => CDashboardHelper::hasTimeSelector($dashboard['pages']), 'time_period' => getTimeSelectorPeriod($time_selector_options), 'clone' => $this->hasInput('clone'), diff --git a/ui/app/views/js/monitoring.dashboard.view.js.php b/ui/app/views/js/monitoring.dashboard.view.js.php index 2c896433e70..9c918318c5e 100644 --- a/ui/app/views/js/monitoring.dashboard.view.js.php +++ b/ui/app/views/js/monitoring.dashboard.view.js.php @@ -33,6 +33,7 @@ dashboard, widget_defaults, widget_last_type, + configuration_hash, has_time_selector, time_period, dynamic, @@ -81,6 +82,7 @@ widget_max_rows: <?= DASHBOARD_WIDGET_MAX_ROWS ?>, widget_defaults, widget_last_type, + configuration_hash, is_editable: dashboard.can_edit_dashboards && dashboard.editable && web_layout_mode != <?= ZBX_LAYOUT_KIOSKMODE ?>, is_edit_mode: dashboard.dashboardid === null || clone, @@ -103,6 +105,7 @@ if (web_layout_mode != <?= ZBX_LAYOUT_KIOSKMODE ?>) { ZABBIX.Dashboard.on(DASHBOARD_EVENT_EDIT, () => this.edit()); ZABBIX.Dashboard.on(DASHBOARD_EVENT_APPLY_PROPERTIES, this.events.applyProperties); + ZABBIX.Dashboard.on(DASHBOARD_EVENT_CONFIGURATION_OUTDATED, this.events.configurationOutdated); if (dynamic.has_dynamic_widgets) { jQuery('#dynamic_hostid').on('change', this.events.dynamicHostChange); @@ -394,6 +397,10 @@ document.getElementById('dashboard-direct-link').textContent = dashboard_data.name; }, + configurationOutdated() { + location.href = location.href; + }, + busy() { view.is_busy = true; view.updateBusy(); diff --git a/ui/app/views/monitoring.dashboard.view.php b/ui/app/views/monitoring.dashboard.view.php index 7545b65bb03..0623b5f8968 100644 --- a/ui/app/views/monitoring.dashboard.view.php +++ b/ui/app/views/monitoring.dashboard.view.php @@ -253,6 +253,7 @@ $html_page 'dashboard' => $data['dashboard'], 'widget_defaults' => $data['widget_defaults'], 'widget_last_type' => $data['widget_last_type'], + 'configuration_hash' => $data['configuration_hash'], 'has_time_selector' => $data['has_time_selector'], 'time_period' => $data['time_period'], 'dynamic' => $data['dynamic'], diff --git a/ui/include/classes/helpers/CDashboardHelper.php b/ui/include/classes/helpers/CDashboardHelper.php index 43aff6b66fa..9dcff28757c 100644 --- a/ui/include/classes/helpers/CDashboardHelper.php +++ b/ui/include/classes/helpers/CDashboardHelper.php @@ -608,4 +608,16 @@ class CDashboardHelper { return $widget_last_type; } + + /** + * @throws JsonException + */ + public static function getConfigurationHash(array $dashboard, array $widget_defaults): string { + ksort($widget_defaults); + + return md5(json_encode([ + array_intersect_key($dashboard, array_flip(['name', 'display_period', 'auto_start', 'pages'])), + $widget_defaults + ], JSON_THROW_ON_ERROR)); + } } diff --git a/ui/include/classes/mvc/CRouter.php b/ui/include/classes/mvc/CRouter.php index 5ea09f1ed75..c9a5f96d0bb 100644 --- a/ui/include/classes/mvc/CRouter.php +++ b/ui/include/classes/mvc/CRouter.php @@ -64,6 +64,7 @@ class CRouter { 'correlation.enable' => ['CControllerCorrelationEnable', null, null], 'correlation.list' => ['CControllerCorrelationList', 'layout.htmlpage', 'configuration.correlation.list'], 'correlation.update' => ['CControllerCorrelationUpdate', null, null], + 'dashboard.configuration.hash.get' => ['CControllerDashboardConfigurationHashGet', 'layout.json', null], 'dashboard.delete' => ['CControllerDashboardDelete', null, null], 'dashboard.list' => ['CControllerDashboardList', 'layout.htmlpage', 'monitoring.dashboard.list'], 'dashboard.page.properties.check' => ['CControllerDashboardPagePropertiesCheck', 'layout.json', null], diff --git a/ui/js/class.dashboard.js b/ui/js/class.dashboard.js index 2be2e1a6e4c..b48f3ddfc95 100644 --- a/ui/js/class.dashboard.js +++ b/ui/js/class.dashboard.js @@ -33,6 +33,7 @@ const DASHBOARD_EVENT_BUSY = 'dashboard-busy'; const DASHBOARD_EVENT_IDLE = 'dashboard-idle'; const DASHBOARD_EVENT_EDIT = 'dashboard-edit'; const DASHBOARD_EVENT_APPLY_PROPERTIES = 'dashboard-apply-properties'; +const DASHBOARD_EVENT_CONFIGURATION_OUTDATED = 'dashboard-configuration-outdated'; class CDashboard extends CBaseComponent { @@ -49,6 +50,7 @@ class CDashboard extends CBaseComponent { widget_max_rows, widget_defaults, widget_last_type = null, + configuration_hash = null, is_editable, is_edit_mode, can_edit_dashboards, @@ -88,6 +90,7 @@ class CDashboard extends CBaseComponent { this._widget_max_rows = widget_max_rows; this._widget_defaults = {...widget_defaults}; this._widget_last_type = widget_last_type; + this._configuration_hash = configuration_hash; this._is_editable = is_editable; this._is_edit_mode = is_edit_mode; this._can_edit_dashboards = can_edit_dashboards; @@ -134,6 +137,11 @@ class CDashboard extends CBaseComponent { this._slideshow_switch_time = null; this._slideshow_timeout_id = null; + this._configuration_check_period = 60000; + this._configuration_check_steady_period = 2000; + this._configuration_check_time = null; + this._configuration_check_timeout_id = null; + this._is_unsaved = false; if (!this._is_kiosk_mode) { @@ -175,8 +183,12 @@ class CDashboard extends CBaseComponent { this._target.classList.add(ZBX_STYLE_DASHBOARD_IS_EDIT_MODE); } - if (!this._is_edit_mode && this._data.auto_start == 1 && this._dashboard_pages.size > 1) { - this._startSlideshow(); + if (!this._is_edit_mode) { + this._startConfigurationChecker(); + + if (this._data.auto_start == 1 && this._dashboard_pages.size > 1) { + this._startSlideshow(); + } } } @@ -199,6 +211,7 @@ class CDashboard extends CBaseComponent { this._tabs.enableSorting(); } + this._stopConfigurationChecker(); this._stopSlideshow(); this._target.classList.add(ZBX_STYLE_DASHBOARD_IS_EDIT_MODE); @@ -284,7 +297,7 @@ class CDashboard extends CBaseComponent { } this._slideshow_switch_time = Math.max(Date.now() + this._slideshow_steady_period, - timeout_ms + this._slideshow_switch_time + this._slideshow_switch_time + timeout_ms ); this._slideshow_timeout_id = setTimeout(() => this._switchSlideshow(), @@ -312,6 +325,100 @@ class CDashboard extends CBaseComponent { } } + _startConfigurationChecker() { + if (this._configuration_check_timeout_id !== null) { + clearTimeout(this._configuration_check_timeout_id); + } + + this._configuration_check_time = Date.now() + this._configuration_check_period; + this._configuration_check_timeout_id = setTimeout(() => this._checkConfiguration(), + this._configuration_check_period + ); + } + + _stopConfigurationChecker() { + if (this._configuration_check_timeout_id === null) { + return; + } + + clearTimeout(this._configuration_check_timeout_id); + + this._configuration_check_time = null; + this._configuration_check_timeout_id = null; + } + + _checkConfiguration() { + this._configuration_check_timeout_id = null; + + if (this._isUserInteracting()) { + this._configuration_check_time = Date.now() + this._configuration_check_steady_period; + this._configuration_check_timeout_id = setTimeout(() => this._checkConfiguration(), + this._configuration_check_steady_period + ); + + return; + } + + const busy_condition = this._createBusyCondition(); + + Promise.resolve() + .then(() => this._promiseCheckConfiguration()) + .catch((exception) => { + console.log('Could not check the dashboard configuration', exception); + }) + .finally(() => { + this._configuration_check_time = Math.max(Date.now() + this._configuration_check_steady_period, + this._configuration_check_time + this._configuration_check_period + ); + + this._configuration_check_timeout_id = setTimeout(() => this._checkConfiguration(), + this._configuration_check_time - Date.now() + ); + + this._deleteBusyCondition(busy_condition); + }); + } + + _promiseCheckConfiguration() { + const curl = new Curl('zabbix.php'); + + curl.setArgument('action', 'dashboard.configuration.hash.get'); + + return fetch(curl.getUrl(), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + dashboardid: this._data.dashboardid + }) + }) + .then((response) => response.json()) + .then((response) => { + if ('error' in response) { + throw {error: response.error}; + } + + if (this._configuration_hash !== response.configuration_hash) { + this.fire(DASHBOARD_EVENT_CONFIGURATION_OUTDATED); + } + }); + } + + _keepSteadyConfigurationChecker() { + if (this._configuration_check_timeout_id === null) { + return; + } + + if (this._configuration_check_timeout_id - Date.now() < this._configuration_check_steady_period) { + clearTimeout(this._configuration_check_timeout_id); + + this._configuration_check_time = Date.now() + this._configuration_check_steady_period; + + this._configuration_check_timeout_id = setTimeout(() => this._checkConfiguration(), + this._configuration_check_time - Date.now() + ); + } + } + _announceWidgets() { const dashboard_pages = Array.from(this._dashboard_pages.keys()); @@ -824,6 +931,8 @@ class CDashboard extends CBaseComponent { if (!this._is_edit_mode) { this._storeSelectedDashboardPage(dashboard_page); + this._keepSteadyConfigurationChecker(); + if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } @@ -831,8 +940,12 @@ class CDashboard extends CBaseComponent { this._promiseSelectDashboardPage(dashboard_page, {is_async}) .then(() => { - if (this._isSlideshowRunning()) { - this._startSlideshow(); + if (!this._is_edit_mode) { + this._keepSteadyConfigurationChecker(); + + if (this._isSlideshowRunning()) { + this._startSlideshow(); + } } }); } @@ -1966,6 +2079,8 @@ class CDashboard extends CBaseComponent { } if (!this._is_edit_mode) { + this._keepSteadyConfigurationChecker(); + if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } |