Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrejs Verza <andrejs.verza@zabbix.com>2022-11-04 19:03:36 +0300
committerAndrejs Verza <andrejs.verza@zabbix.com>2022-11-04 19:03:36 +0300
commit8728520cd26b9f9bce8631e8006d8e2bb8321f47 (patch)
tree6154203eb4c2bc6f9fe53129c7dce97e7f65ef33
parent36c0a0d2147b692645e389d8145d5677c72b657a (diff)
..F....... [ZBXNEXT-7469] implemented dashboard configuration monitoring
-rw-r--r--ui/app/controllers/CControllerDashboardConfigurationHashGet.php84
-rw-r--r--ui/app/controllers/CControllerDashboardView.php10
-rw-r--r--ui/app/views/js/monitoring.dashboard.view.js.php7
-rw-r--r--ui/app/views/monitoring.dashboard.view.php1
-rw-r--r--ui/include/classes/helpers/CDashboardHelper.php12
-rw-r--r--ui/include/classes/mvc/CRouter.php1
-rw-r--r--ui/js/class.dashboard.js125
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();
}