diff options
Diffstat (limited to 'ui/js/class.dashboard.js')
-rw-r--r-- | ui/js/class.dashboard.js | 371 |
1 files changed, 280 insertions, 91 deletions
diff --git a/ui/js/class.dashboard.js b/ui/js/class.dashboard.js index 2a5c3a09e4e..25a03c2b751 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 { @@ -48,6 +49,8 @@ class CDashboard extends CBaseComponent { widget_min_rows, widget_max_rows, widget_defaults, + widget_last_type = null, + configuration_hash = null, is_editable, is_edit_mode, can_edit_dashboards, @@ -85,7 +88,9 @@ class CDashboard extends CBaseComponent { this._max_rows = max_rows; this._widget_min_rows = widget_min_rows; this._widget_max_rows = widget_max_rows; - this._widget_defaults = widget_defaults; + 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; @@ -132,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) { @@ -173,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(); + } } } @@ -197,6 +211,7 @@ class CDashboard extends CBaseComponent { this._tabs.enableSorting(); } + this._stopConfigurationChecker(); this._stopSlideshow(); this._target.classList.add(ZBX_STYLE_DASHBOARD_IS_EDIT_MODE); @@ -282,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(), @@ -310,6 +325,101 @@ 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.config.hash'); + + return fetch(curl.getUrl(), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + templateid: this._data.templateid ?? undefined, + dashboardid: this._data.dashboardid + }) + }) + .then((response) => response.json()) + .then((response) => { + if ('error' in response) { + throw {error: response.error}; + } + + if (response.configuration_hash !== null && 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_time - 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()); @@ -502,16 +612,26 @@ class CDashboard extends CBaseComponent { } pasteDashboardPage(new_dashboard_page_data) { + this._clearWarnings(); + if (this._dashboard_pages.size >= this._max_dashboard_pages) { this._warnDashboardExhausted(); return; } + const widgets = []; + + for (const widget of new_dashboard_page_data.widgets) { + if (widget.type in this._widget_defaults) { + widgets.push(widget); + } + } + const busy_condition = this._createBusyCondition(); return Promise.resolve() - .then(() => this._promiseDashboardWidgetsSanitize(new_dashboard_page_data.widgets)) + .then(() => this._promiseDashboardWidgetsSanitize(widgets)) .then((response) => { if (this._dashboard_pages.size >= this._max_dashboard_pages) { this._warnDashboardExhausted(); @@ -519,31 +639,42 @@ class CDashboard extends CBaseComponent { return; } - const widgets = new_dashboard_page_data.widgets; + if (response.widgets.length < new_dashboard_page_data.widgets.length) { + this._warn(t('Inaccessible widgets were not pasted.')); + } + + const sane_widgets = []; for (let i = 0; i < response.widgets.length; i++) { - widgets[i].fields = response.widgets[i].fields; + if (response.widgets[i] !== null) { + sane_widgets.push({ + ...widgets[i], + fields: response.widgets[i].fields + }); + } } const used_references = this._getUsedReferences(); const reference_substitution = new Map(); - for (const widget of widgets) { - const reference_field = this._widget_defaults[widget.type].reference_field; + for (const widget of sane_widgets) { + const widget_class = eval(this._widget_defaults[widget.type].js_class); - if (reference_field !== null) { - const old_reference = widget.fields[reference_field]; + if (widget_class.hasReferenceField()) { + const old_reference = widget.fields.reference; const new_reference = this._createReference({used_references}); - widget.fields[reference_field] = new_reference; + widget.fields.reference = new_reference; used_references.add(new_reference); reference_substitution.set(old_reference, new_reference); } } - for (const widget of widgets) { - for (const reference_field of this._widget_defaults[widget.type].foreign_reference_fields) { + for (const widget of sane_widgets) { + const widget_class = eval(this._widget_defaults[widget.type].js_class); + + for (const reference_field of widget_class.getForeignReferenceFields()) { const old_reference = widget.fields[reference_field]; if (reference_substitution.has(old_reference)) { @@ -556,7 +687,7 @@ class CDashboard extends CBaseComponent { dashboard_pageid: null, name: new_dashboard_page_data.name, display_period: new_dashboard_page_data.display_period, - widgets + widgets: sane_widgets }); this._selectDashboardPage(dashboard_page, {is_async: true}); @@ -583,6 +714,14 @@ class CDashboard extends CBaseComponent { } pasteWidget(new_widget_data, {widget = null, new_widget_pos = null} = {}) { + this._clearWarnings(); + + if (!(new_widget_data.type in this._widget_defaults)) { + this._warn(t('Cannot paste inaccessible widget.')); + + return; + } + const dashboard_page = this._selected_dashboard_page; if (widget !== null) { @@ -608,27 +747,29 @@ class CDashboard extends CBaseComponent { return; } + let old_widget_data = null; + if (widget !== null) { + old_widget_data = widget.getDataCopy({is_single_copy: false}); + dashboard_page.deleteWidget(widget, {is_batch_mode: true}); } - const reference_field = this._widget_defaults[new_widget_data.type].reference_field; + const new_widget_class = eval(this._widget_defaults[new_widget_data.type].js_class); - if (reference_field !== null) { - new_widget_data.fields[reference_field] = this._createReference(); + if (new_widget_class.hasReferenceField()) { + new_widget_data.fields.reference = this._createReference(); } let references = []; for (const widget of dashboard_page.getWidgets()) { - const reference_field = this._widget_defaults[widget.getType()].reference_field; - - if (reference_field !== null) { - references.push(widget.getFields()[reference_field]); + if (widget.constructor.hasReferenceField()) { + references.push(widget.getFields()['reference']); } } - for (const reference_field of this._widget_defaults[new_widget_data.type].foreign_reference_fields) { + for (const reference_field of new_widget_class.getForeignReferenceFields()) { if (reference_field in new_widget_data.fields && !references.includes(new_widget_data.fields[reference_field])) { new_widget_data.fields[reference_field] = ''; @@ -654,6 +795,24 @@ class CDashboard extends CBaseComponent { return; } + if (response.widgets[0] === null) { + if (widget !== null) { + dashboard_page.replaceWidget(paste_placeholder_widget, { + ...old_widget_data, + widgetid: widget.getWidgetId(), + is_new: false, + unique_id: widget.getUniqueId() + }); + } + else { + dashboard_page.deleteWidget(paste_placeholder_widget); + } + + this._warn(t('Cannot paste inaccessible widget.')); + + return; + } + dashboard_page.replaceWidget(paste_placeholder_widget, { ...new_widget_data, fields: response.widgets[0].fields, @@ -691,7 +850,7 @@ class CDashboard extends CBaseComponent { for (const widget_data of widgets_data) { request_widgets_data.push({ type: widget_data.type, - fields: JSON.stringify(widget_data.fields) + fields: widget_data.fields }); } @@ -787,6 +946,8 @@ class CDashboard extends CBaseComponent { if (!this._is_edit_mode) { this._storeSelectedDashboardPage(dashboard_page); + this._keepSteadyConfigurationChecker(); + if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } @@ -794,8 +955,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(); + } } }); } @@ -1127,7 +1292,19 @@ class CDashboard extends CBaseComponent { } editWidgetProperties(properties = {}, {new_widget_pos = null} = {}) { - const overlay = PopUp('dashboard.widget.edit', { + this._clearWarnings(); + + if (properties.type === undefined) { + properties.type = this._widget_last_type; + + if (properties.type === null) { + this._warn(t('Cannot add widget: no widgets available.')); + + return; + } + } + + const overlay = PopUp(`widget.${properties.type}.edit`, { templateid: this._data.templateid ?? undefined, ...properties }, { @@ -1184,6 +1361,18 @@ class CDashboard extends CBaseComponent { } } + document.getElementById('type').addEventListener('change', () => this.reloadWidgetProperties()); + + form.addEventListener('change', (e) => { + const do_trim = e.target.matches( + 'input[type="text"]:not([data-no-trim="1"]), textarea:not([data-no-trim="1"])' + ); + + if (do_trim) { + e.target.value = e.target.value.trim(); + } + }, {capture: true}); + try { new TabIndicators(); } @@ -1207,12 +1396,11 @@ class CDashboard extends CBaseComponent { const properties = { type: fields.type, - prev_type: overlay.data.original_properties.type, unique_id: overlay.data.original_properties.unique_id ?? undefined, dashboard_page_unique_id: overlay.data.original_properties.dashboard_page_unique_id ?? undefined }; - if (properties.type === properties.prev_type) { + if (properties.type === overlay.data.original_properties.type) { properties.name = fields.name; properties.view_mode = fields.show_header == 1 ? ZBX_WIDGET_VIEW_MODE_NORMAL @@ -1222,9 +1410,11 @@ class CDashboard extends CBaseComponent { delete fields.name; delete fields.show_header; - properties.fields = JSON.stringify(fields); + properties.fields = fields; } + overlay.$dialogue[0].dispatchEvent(new CustomEvent('overlay.reload')); + this.editWidgetProperties(properties, {new_widget_pos: this._new_widget_pos}); } @@ -1256,18 +1446,24 @@ class CDashboard extends CBaseComponent { return Promise.resolve() .then(() => this._promiseDashboardWidgetCheck({templateid, type, name, view_mode, fields})) - .then(() => this._promiseDashboardWidgetConfigure({templateid, type, view_mode, fields})) - .then((configuration) => { + .then(() => { overlayDialogueDestroy(overlay.dialogueid); if (widget !== null && widget.getType() === type) { - widget.updateProperties({name, view_mode, fields, configuration}); + widget.updateProperties({name, view_mode, fields}); return; } - if (this._widget_defaults[type].reference_field !== null) { - fields[this._widget_defaults[type].reference_field] = this._createReference(); + if (type !== this._widget_last_type) { + this._widget_last_type = type; + updateUserProfile('web.dashboard.last_widget_type', type, [], PROFILE_TYPE_STR); + } + + const widget_class = eval(this._widget_defaults[type].js_class); + + if (widget_class.hasReferenceField()) { + fields.reference = this._createReference(); } const widget_data = { @@ -1275,7 +1471,6 @@ class CDashboard extends CBaseComponent { name, view_mode, fields, - configuration, widgetid: null, pos: widget === null ? this._new_widget_pos_reserved : widget.getPos(), is_new: widget === null, @@ -1332,45 +1527,25 @@ class CDashboard extends CBaseComponent { }); } - _promiseDashboardWidgetCheck({templateid, type, name, view_mode, fields}) { - const fields_str = Object.keys(fields).length > 0 ? JSON.stringify(fields) : undefined; - - const curl = new Curl('zabbix.php'); - - curl.setArgument('action', 'dashboard.widget.check'); - - return fetch(curl.getUrl(), { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({templateid, type, name, view_mode, fields: fields_str}) - }) - .then((response) => response.json()) - .then((response) => { - if ('error' in response) { - throw {error: response.error}; - } - }); + _isEditingWidgetProperties() { + return this._is_edit_widget_properties_cancel_subscribed; } - _promiseDashboardWidgetConfigure({templateid, type, view_mode, fields}) { - const fields_str = Object.keys(fields).length > 0 ? JSON.stringify(fields) : undefined; - + _promiseDashboardWidgetCheck({templateid, type, name, view_mode, fields}) { const curl = new Curl('zabbix.php'); - curl.setArgument('action', 'dashboard.widget.configure'); + curl.setArgument('action', 'dashboard.widget.check'); return fetch(curl.getUrl(), { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({templateid, type, view_mode, fields: fields_str}) + body: JSON.stringify({templateid, type, name, view_mode, fields}) }) .then((response) => response.json()) .then((response) => { if ('error' in response) { throw {error: response.error}; } - - return response.configuration; }); } @@ -1385,7 +1560,26 @@ class CDashboard extends CBaseComponent { if (this._can_edit_dashboards) { menu_actions.push({ label: t('Copy'), - clickCallback: () => this._storeDashboardPageDataCopy(dashboard_page.getDataCopy()) + clickCallback: () => { + this._clearWarnings(); + + const data_copy = dashboard_page.getDataCopy(); + const data_copy_widgets = data_copy.widgets; + + data_copy.widgets = []; + + for (const widget of data_copy_widgets) { + if (widget.type in this._widget_defaults) { + data_copy.widgets.push(widget); + } + } + + this._storeDashboardPageDataCopy(data_copy); + + if (data_copy.widgets.length < data_copy_widgets.length) { + this._warn(t('Inaccessible widgets were not copied.')); + } + } }); } @@ -1430,25 +1624,23 @@ class CDashboard extends CBaseComponent { // Dashboard view methods. - _warnDashboardExhausted() { + _warn(warning) { this._clearWarnings(); - this._warning_message_box = makeMessageBox('warning', [], sprintf( + this._warning_message_box = makeMessageBox('warning', [], warning); + + addMessage(this._warning_message_box); + } + + _warnDashboardExhausted() { + this._warn(sprintf( t('Cannot add dashboard page: maximum number of %1$d dashboard pages has been added.'), this._max_dashboard_pages )); - - addMessage(this._warning_message_box); } _warnDashboardPageExhausted() { - this._clearWarnings(); - - this._warning_message_box = makeMessageBox('warning', [], - t('Cannot add widget: not enough free space on the dashboard.') - ); - - addMessage(this._warning_message_box); + this._warn(t('Cannot add widget: not enough free space on the dashboard.')); } _clearWarnings() { @@ -1676,14 +1868,13 @@ class CDashboard extends CBaseComponent { for (const dashboard_page of this._dashboard_pages.keys()) { for (const widget of dashboard_page.getWidgets()) { - const type = widget.getType(); const fields = widget.getFields(); - if (this._widget_defaults[type].reference_field !== null) { - used_references.add(fields[this._widget_defaults[type].reference_field]); + if (widget.constructor.hasReferenceField()) { + used_references.add(fields.reference); } - for (const reference_field of this._widget_defaults[type].foreign_reference_fields) { + for (const reference_field of widget.constructor.getForeignReferenceFields()) { used_references.add(fields[reference_field]); } } @@ -1704,31 +1895,23 @@ class CDashboard extends CBaseComponent { }, dashboardPageWidgetAdd: (e) => { + const dashboard_page = this._selected_dashboard_page; + const new_widget_data = this.getStoredWidgetDataCopy(); const new_widget_pos = e.detail.new_widget_pos; if (new_widget_data !== null) { - const dashboard_page = this._selected_dashboard_page; - - let menu_was_cancelled = true; - const menu = [ { label: t('Actions'), items: [ { label: t('Add widget'), - clickCallback: () => { - this.editWidgetProperties({}, {new_widget_pos}); - menu_was_cancelled = false; - } + clickCallback: () => this.editWidgetProperties({}, {new_widget_pos}) }, { label: t('Paste widget'), - clickCallback: () => { - this.pasteWidget(new_widget_data, {new_widget_pos}); - menu_was_cancelled = false; - } + clickCallback: () => this.pasteWidget(new_widget_data, {new_widget_pos}) } ] } @@ -1741,7 +1924,7 @@ class CDashboard extends CBaseComponent { jQuery(placeholder).menuPopup(menu, placeholder_event, { closeCallback: () => { - if (menu_was_cancelled) { + if (!this._isEditingWidgetProperties()) { dashboard_page.resetWidgetPlaceholder(); } } @@ -1749,6 +1932,10 @@ class CDashboard extends CBaseComponent { } else { this.editWidgetProperties({}, {new_widget_pos}); + + if (!this._isEditingWidgetProperties()) { + dashboard_page.resetWidgetPlaceholder(); + } } }, @@ -1780,7 +1967,7 @@ class CDashboard extends CBaseComponent { type: widget.getType(), name: widget.getName(), view_mode: widget.getViewMode(), - fields: JSON.stringify(widget.getFields()), + fields: widget.getFields(), unique_id: widget.getUniqueId(), dashboard_page_unique_id: dashboard_page.getUniqueId() }); @@ -1907,6 +2094,8 @@ class CDashboard extends CBaseComponent { } if (!this._is_edit_mode) { + this._keepSteadyConfigurationChecker(); + if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } |