/* ** Zabbix ** Copyright (C) 2001-2023 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. **/ const ZBX_STYLE_BTN_DASHBOARD_PAGE_PROPERTIES = 'btn-dashboard-page-properties'; const ZBX_STYLE_DASHBOARD_IS_MULTIPAGE = 'dashboard-is-multipage'; const ZBX_STYLE_DASHBOARD_IS_EDIT_MODE = 'dashboard-is-edit-mode'; const ZBX_STYLE_DASHBOARD_NAVIGATION_IS_SCROLLABLE = 'is-scrollable'; const ZBX_STYLE_DASHBOARD_SELECTED_TAB = 'selected-tab'; const DASHBOARD_STATE_INITIAL = 'initial'; const DASHBOARD_STATE_ACTIVE = 'active'; const DASHBOARD_CLIPBOARD_TYPE_WIDGET = 'widget'; const DASHBOARD_CLIPBOARD_TYPE_DASHBOARD_PAGE = 'dashboard-page'; 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 { constructor(target, { containers, buttons, data, max_dashboard_pages, cell_width, cell_height, max_columns, max_rows, widget_min_rows, widget_max_rows, widget_defaults, widget_last_type = null, configuration_hash = null, is_editable, is_edit_mode, can_edit_dashboards, is_kiosk_mode, time_period, dynamic_hostid, csrf_token = null }) { this._target = target; this._containers = { grid: containers.grid, navigation: containers.navigation, navigation_tabs: containers.navigation_tabs } this._buttons = { previous_page: buttons.previous_page, next_page: buttons.next_page, slideshow: buttons.slideshow }; this._data = { dashboardid: data.dashboardid, name: data.name, userid: data.userid, templateid: data.templateid, display_period: data.display_period, auto_start: data.auto_start }; this._max_dashboard_pages = max_dashboard_pages; this._cell_width = cell_width; this._cell_height = cell_height; this._max_columns = max_columns; 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_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; this._is_kiosk_mode = is_kiosk_mode; this._time_period = time_period; this._dynamic_hostid = dynamic_hostid; this._csrf_token = csrf_token; this._init(); this._registerEvents(); } _init() { this._state = DASHBOARD_STATE_INITIAL; this._dashboard_pages = new Map(); this._selected_dashboard_page = null; this._busy_conditions = new Set(); this._async_timeout_ms = 50; this._unique_id_index = 0; this._new_widget_dashboard_page = null; this._new_widget_pos = null; this._new_widget_pos_reserved = null; this._warning_message_box = null; this._reserve_header_lines = 0; this._reserve_header_lines_timeout_id = null; this._is_edit_widget_properties_cancel_subscribed = false; this._header_lines_steady_period = 2000; this._slideshow_steady_period = 5000; 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) { const sortable = document.createElement('div'); this._containers.navigation_tabs.appendChild(sortable); this._tabs = new CSortable(sortable, { is_vertical: false, is_sorting_enabled: this._is_edit_mode }); this._tabs_dashboard_pages = new Map(); } } // Logical state control methods. activate() { if (this._dashboard_pages.size == 0) { throw new Error('Cannot activate dashboard without dashboard pages.'); } this._state = DASHBOARD_STATE_ACTIVE; this._activateEvents(); this._announceWidgets(); const dashboard_page = this._getInitialDashboardPage(); this._selectDashboardPage(dashboard_page); if (this._is_edit_mode) { this._target.classList.add(ZBX_STYLE_DASHBOARD_IS_EDIT_MODE); } if (!this._is_edit_mode) { this._startConfigurationChecker(); if (this._data.auto_start == 1 && this._dashboard_pages.size > 1) { this._startSlideshow(); } } } // External events management methods. isEditMode() { return this._is_edit_mode; } setEditMode({is_internal_call = false} = {}) { this._is_edit_mode = true; for (const dashboard_page of this._dashboard_pages.keys()) { if (!dashboard_page.isEditMode()) { dashboard_page.setEditMode(); } } if (!this._is_kiosk_mode) { this._tabs.enableSorting(); } this._stopConfigurationChecker(); this._stopSlideshow(); this._target.classList.add(ZBX_STYLE_DASHBOARD_IS_EDIT_MODE); if (is_internal_call) { this.fire(DASHBOARD_EVENT_EDIT); } } setDynamicHost(dynamic_hostid) { this._dynamic_hostid = dynamic_hostid; for (const dashboard_page of this._dashboard_pages.keys()) { dashboard_page.setDynamicHost(this._dynamic_hostid); } } _startSlideshow() { if (this._slideshow_timeout_id !== null) { clearTimeout(this._slideshow_timeout_id); } if (this._buttons.slideshow !== null) { if (this._is_kiosk_mode) { this._buttons.slideshow.classList.remove(ZBX_ICON_PLAY); this._buttons.slideshow.classList.add(ZBX_ICON_PAUSE); } this._buttons.slideshow.classList.remove('slideshow-state-stopped'); this._buttons.slideshow.classList.add('slideshow-state-started'); if (this._buttons.slideshow.title !== '') { this._buttons.slideshow.title = t('Stop slideshow'); } } let timeout_ms = this._selected_dashboard_page.getDisplayPeriod() * 1000; if (timeout_ms == 0) { timeout_ms = this._data.display_period * 1000; } this._slideshow_switch_time = Date.now() + timeout_ms; this._slideshow_timeout_id = setTimeout(() => this._switchSlideshow(), timeout_ms); } _stopSlideshow() { if (this._slideshow_timeout_id === null) { return; } if (this._buttons.slideshow !== null) { if (this._is_kiosk_mode) { this._buttons.slideshow.classList.remove(ZBX_ICON_PAUSE); this._buttons.slideshow.classList.add(ZBX_ICON_PLAY); } this._buttons.slideshow.classList.remove('slideshow-state-started'); this._buttons.slideshow.classList.add('slideshow-state-stopped'); if (this._buttons.slideshow.title !== '') { this._buttons.slideshow.title = t('Start slideshow'); } } clearTimeout(this._slideshow_timeout_id); this._slideshow_switch_time = null; this._slideshow_timeout_id = null; } _switchSlideshow() { this._slideshow_timeout_id = null; if (this._isUserInteracting()) { this._slideshow_switch_time = Date.now() + this._slideshow_steady_period; this._slideshow_timeout_id = setTimeout(() => this._switchSlideshow(), this._slideshow_steady_period); return; } const dashboard_pages = [...this._dashboard_pages.keys()]; const dashboard_page_index = dashboard_pages.indexOf(this._selected_dashboard_page); this._selectDashboardPage( dashboard_pages[dashboard_page_index < dashboard_pages.length - 1 ? dashboard_page_index + 1 : 0] ) let timeout_ms = this._selected_dashboard_page.getDisplayPeriod() * 1000; if (timeout_ms == 0) { timeout_ms = this._data.display_period * 1000; } this._slideshow_switch_time = Math.max(Date.now() + this._slideshow_steady_period, this._slideshow_switch_time + timeout_ms ); this._slideshow_timeout_id = setTimeout(() => this._switchSlideshow(), this._slideshow_switch_time - Date.now() ); } _isSlideshowRunning() { return this._slideshow_timeout_id !== null; } _keepSteadySlideshow() { if (this._slideshow_timeout_id === null) { return; } if (this._slideshow_switch_time - Date.now() < this._slideshow_steady_period) { clearTimeout(this._slideshow_timeout_id); this._slideshow_switch_time = Date.now() + this._slideshow_steady_period; this._slideshow_timeout_id = setTimeout(() => this._switchSlideshow(), this._slideshow_switch_time - Date.now() ); } } _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()); for (const dashboard_page of dashboard_pages) { dashboard_page.announceWidgets(dashboard_pages); } } _createBusyCondition() { if (this._busy_conditions.size == 0) { this.fire(DASHBOARD_EVENT_BUSY); } const busy_condition = {}; this._busy_conditions.add(busy_condition); return busy_condition; } _deleteBusyCondition(busy_condition) { this._busy_conditions.delete(busy_condition); if (this._busy_conditions.size == 0) { this.fire(DASHBOARD_EVENT_IDLE); } } isUnsaved() { if (this._is_unsaved) { return true; } for (const dashboard_page of this._dashboard_pages.keys()) { if (dashboard_page.isUnsaved()) { return true; } } return false; } // Data interface methods. getData() { return this._data; } addNewDashboardPage() { if (this._dashboard_pages.size >= this._max_dashboard_pages) { this._warnDashboardExhausted(); return; } this.editDashboardPageProperties(); } addNewWidget() { this.editWidgetProperties(); } addDashboardPage({dashboard_pageid, name, display_period, widgets}) { const dashboard_page = new CDashboardPage(this._containers.grid, { data: { dashboard_pageid, name, display_period }, dashboard: { templateid: this._data.templateid, dashboardid: this._data.dashboardid }, cell_width: this._cell_width, cell_height: this._cell_height, max_columns: this._max_columns, max_rows: this._max_rows, widget_min_rows: this._widget_min_rows, widget_max_rows: this._widget_max_rows, widget_defaults: this._widget_defaults, is_editable: this._is_editable, is_edit_mode: this._is_edit_mode, can_edit_dashboards: this._can_edit_dashboards, time_period: this._time_period, dynamic_hostid: this._dynamic_hostid, csrf_token: this._csrf_token, unique_id: this._createUniqueId() }); this._dashboard_pages.set(dashboard_page, {}); for (const widget_data of widgets) { dashboard_page.addWidget({ ...widget_data, is_new: false, unique_id: this._createUniqueId() }); } if (this._state === DASHBOARD_STATE_ACTIVE) { this._announceWidgets(); } if (!this._is_kiosk_mode) { this._addTab(dashboard_page); } this._target.classList.toggle(ZBX_STYLE_DASHBOARD_IS_MULTIPAGE, this._dashboard_pages.size > 1); if (dashboard_pageid === null) { this._is_unsaved = true; } return dashboard_page; } deleteDashboardPage(dashboard_page) { if (this._dashboard_pages.size == 1) { throw new Error('Cannot delete the last dashboard page.'); } if (dashboard_page === this._selected_dashboard_page) { if (this._is_kiosk_mode) { for (const select_dashboard_page of this._dashboard_pages.keys()) { if (select_dashboard_page !== dashboard_page) { this._selectDashboardPage(select_dashboard_page); break; } } } else { const tabs = [...this._tabs.getList().children]; const tab_index = tabs.indexOf(this._dashboard_pages.get(dashboard_page).tab); this._selectDashboardPage( this._tabs_dashboard_pages.get(tabs[tab_index > 0 ? tab_index - 1 : tab_index + 1]) ); } } if (dashboard_page.getState() !== DASHBOARD_PAGE_STATE_INITIAL) { dashboard_page.destroy(); } if (!this._is_kiosk_mode) { this._deleteTab(dashboard_page); } this._dashboard_pages.delete(dashboard_page); this._announceWidgets(); this._target.classList.toggle(ZBX_STYLE_DASHBOARD_IS_MULTIPAGE, this._dashboard_pages.size > 1); this._is_unsaved = true; } getDashboardPage(unique_id) { for (const dashboard_page of this._dashboard_pages.keys()) { if (dashboard_page.getUniqueId() === unique_id) { return dashboard_page; } } return null; } getDashboardPages() { return [...this._dashboard_pages.keys()]; } 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(widgets)) .then((response) => { if (this._dashboard_pages.size >= this._max_dashboard_pages) { this._warnDashboardExhausted(); return; } 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++) { 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 sane_widgets) { const widget_class = eval(this._widget_defaults[widget.type].js_class); if (widget_class.hasReferenceField()) { const old_reference = widget.fields.reference; const new_reference = this._createReference({used_references}); widget.fields.reference = new_reference; used_references.add(new_reference); reference_substitution.set(old_reference, new_reference); } } 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)) { widget.fields[reference_field] = reference_substitution.get(old_reference); } } } const dashboard_page = this.addDashboardPage({ dashboard_pageid: null, name: new_dashboard_page_data.name, display_period: new_dashboard_page_data.display_period, widgets: sane_widgets }); this._selectDashboardPage(dashboard_page, {is_async: true}); }) .catch((exception) => { clearMessages(); let title; let messages = []; if (typeof exception === 'object' && 'error' in exception) { title = exception.error.title; messages = exception.error.messages; } else { title = t('Failed to paste dashboard page.'); } const message_box = makeMessageBox('bad', messages, title); addMessage(message_box); }) .finally(() => this._deleteBusyCondition(busy_condition)) } 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) { new_widget_pos = widget.getPos(); } else if (new_widget_pos !== null) { new_widget_pos = { ...new_widget_data.pos, ...new_widget_pos }; new_widget_pos.width = Math.min(new_widget_pos.width, this._max_columns - new_widget_pos.x); new_widget_pos.height = Math.min(new_widget_pos.height, this._max_rows - new_widget_pos.y); new_widget_pos = dashboard_page.accommodatePos(new_widget_pos); } else { new_widget_pos = dashboard_page.findFreePos(new_widget_data.pos); } if (new_widget_pos === null) { this._warnDashboardPageExhausted(); 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 new_widget_class = eval(this._widget_defaults[new_widget_data.type].js_class); if (new_widget_class.hasReferenceField()) { new_widget_data.fields.reference = this._createReference(); } let references = []; for (const widget of dashboard_page.getWidgets()) { if (widget.constructor.hasReferenceField()) { references.push(widget.getFields()['reference']); } } 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] = ''; } } const paste_placeholder_widget = dashboard_page.addPastePlaceholderWidget({ type: new_widget_data.type, name: new_widget_data.name, view_mode: new_widget_data.view_mode, pos: new_widget_pos, unique_id: this._createUniqueId() }); dashboard_page.resetWidgetPlaceholder(); const busy_condition = this._createBusyCondition(); dashboard_page.promiseScrollIntoView(new_widget_pos) .then(() => this._promiseDashboardWidgetsSanitize([new_widget_data])) .then((response) => { if (dashboard_page.getState() === DASHBOARD_PAGE_STATE_DESTROYED) { 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, widgetid: null, pos: new_widget_pos, is_new: true, unique_id: this._createUniqueId() }); }) .catch((exception) => { dashboard_page.deleteWidget(paste_placeholder_widget); clearMessages(); let title, messages; if (typeof exception === 'object' && 'error' in exception) { title = exception.error.title; messages = exception.error.messages; } else { title = t('Failed to paste widget.'); } const message_box = makeMessageBox('bad', messages, title); addMessage(message_box); }) .finally(() => this._deleteBusyCondition(busy_condition)); } _promiseDashboardWidgetsSanitize(widgets_data) { let request_widgets_data = []; for (const widget_data of widgets_data) { request_widgets_data.push({ type: widget_data.type, fields: widget_data.fields }); } const curl = new Curl('zabbix.php'); curl.setArgument('action', 'dashboard.widgets.sanitize'); return fetch(curl.getUrl(), { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({widgets: request_widgets_data}) }) .then((response) => response.json()) .then((response) => { if ('error' in response) { throw {error: response.error}; } return response; }); } _storeDashboardObject(data) { localStorage.setItem('dashboard.clipboard', JSON.stringify({ zbx_session_name: window.ZBX_SESSION_NAME, data })); } _getStoredDashboardObject() { let clipboard = localStorage.getItem('dashboard.clipboard'); if (clipboard === null) { return null; } clipboard = JSON.parse(clipboard); if (clipboard.zbx_session_name !== window.ZBX_SESSION_NAME) { return null; } return clipboard.data; } _storeWidgetDataCopy(widget_data) { this._storeDashboardObject({ type: DASHBOARD_CLIPBOARD_TYPE_WIDGET, data: widget_data }); } getStoredWidgetDataCopy() { const data = this._getStoredDashboardObject(); if (data === null || data.type !== DASHBOARD_CLIPBOARD_TYPE_WIDGET) { return null; } const widget_data = data.data; if (widget_data.dashboard.templateid !== this._data.templateid) { return null; } return widget_data; } _storeDashboardPageDataCopy(dashboard_page_data) { this._storeDashboardObject({ type: DASHBOARD_CLIPBOARD_TYPE_DASHBOARD_PAGE, data: dashboard_page_data }); } getStoredDashboardPageDataCopy() { const data = this._getStoredDashboardObject(); if (data === null || data.type !== DASHBOARD_CLIPBOARD_TYPE_DASHBOARD_PAGE) { return null; } const dashboard_page_data = data.data; if (dashboard_page_data.dashboard.templateid !== this._data.templateid) { return null; } return dashboard_page_data; } _selectDashboardPage(dashboard_page, {is_async = false} = {}) { if (this._data.templateid === null || !this._is_edit_mode) { this._setInitialDashboardPage(dashboard_page); } if (!this._is_edit_mode) { this._keepSteadyConfigurationChecker(); if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } } this._promiseSelectDashboardPage(dashboard_page, {is_async}) .then(() => { if (!this._is_edit_mode) { this._keepSteadyConfigurationChecker(); if (this._isSlideshowRunning()) { this._startSlideshow(); } } }); } _promiseSelectDashboardPage(dashboard_page, {is_async = false} = {}) { return new Promise((resolve) => { if (this._is_kiosk_mode) { this._doSelectDashboardPage(dashboard_page); resolve(); } else { this._selectTab(dashboard_page); if (is_async) { setTimeout(() => { this._doSelectDashboardPage(dashboard_page); resolve(); }, this._async_timeout_ms); } else { this._doSelectDashboardPage(dashboard_page); resolve(); } } }); } _doSelectDashboardPage(dashboard_page) { if (this._selected_dashboard_page !== null) { this._deactivatePage(this._selected_dashboard_page); } this._selected_dashboard_page = dashboard_page; if (this._selected_dashboard_page.getState() === DASHBOARD_PAGE_STATE_INITIAL) { this._selected_dashboard_page.start(); } this._activatePage(this._selected_dashboard_page); if (this._is_kiosk_mode) { this._resetHeaderLines(); } } _activatePage(dashboard_page) { dashboard_page.activate(); dashboard_page .on(DASHBOARD_PAGE_EVENT_EDIT, this._events.dashboardPageEdit) .on(DASHBOARD_PAGE_EVENT_WIDGET_ADD, this._events.dashboardPageWidgetAdd) .on(DASHBOARD_PAGE_EVENT_WIDGET_ADD_NEW, this._events.dashboardPageWidgetAddNew) .on(DASHBOARD_PAGE_EVENT_WIDGET_DELETE, this._events.dashboardPageWidgetDelete) .on(DASHBOARD_PAGE_EVENT_WIDGET_POSITION, this._events.dashboardPageWidgetPosition) .on(DASHBOARD_PAGE_EVENT_WIDGET_ACTIONS, this._events.dashboardPageWidgetActions) .on(DASHBOARD_PAGE_EVENT_WIDGET_EDIT, this._events.dashboardPageWidgetEdit) .on(DASHBOARD_PAGE_EVENT_WIDGET_COPY, this._events.dashboardPageWidgetCopy) .on(DASHBOARD_PAGE_EVENT_WIDGET_PASTE, this._events.dashboardPageWidgetPaste) .on(DASHBOARD_PAGE_EVENT_ANNOUNCE_WIDGETS, this._events.dashboardPageAnnounceWidgets); if (this._is_kiosk_mode) { dashboard_page.on(DASHBOARD_PAGE_EVENT_RESERVE_HEADER_LINES, this._events.dashboardPageReserveHeaderLines); } } _deactivatePage(dashboard_page) { dashboard_page.deactivate(); dashboard_page .off(DASHBOARD_PAGE_EVENT_EDIT, this._events.dashboardPageEdit) .off(DASHBOARD_PAGE_EVENT_WIDGET_ADD, this._events.dashboardPageWidgetAdd) .off(DASHBOARD_PAGE_EVENT_WIDGET_ADD_NEW, this._events.dashboardPageWidgetAddNew) .off(DASHBOARD_PAGE_EVENT_WIDGET_DELETE, this._events.dashboardPageWidgetDelete) .off(DASHBOARD_PAGE_EVENT_WIDGET_POSITION, this._events.dashboardPageWidgetPosition) .off(DASHBOARD_PAGE_EVENT_WIDGET_ACTIONS, this._events.dashboardPageWidgetActions) .off(DASHBOARD_PAGE_EVENT_WIDGET_EDIT, this._events.dashboardPageWidgetEdit) .off(DASHBOARD_PAGE_EVENT_WIDGET_COPY, this._events.dashboardPageWidgetCopy) .off(DASHBOARD_PAGE_EVENT_WIDGET_PASTE, this._events.dashboardPageWidgetPaste) .off(DASHBOARD_PAGE_EVENT_ANNOUNCE_WIDGETS, this._events.dashboardPageAnnounceWidgets); if (this._is_kiosk_mode) { dashboard_page.off(DASHBOARD_PAGE_EVENT_RESERVE_HEADER_LINES, this._events.dashboardPageReserveHeaderLines); } } _setInitialDashboardPage(dashboard_page) { const dashboard_page_index = this.getDashboardPageIndex(dashboard_page); const url = new URL(location.href); if (dashboard_page_index > 0) { url.searchParams.set('page', dashboard_page_index + 1); } else { url.searchParams.delete('page'); } history.replaceState(null, null, url); } _getInitialDashboardPage() { const url = new URL(location.href); if (url.searchParams.has('page')) { const dashboard_pages = [...this._dashboard_pages.keys()]; const dashboard_page_index = parseInt(url.searchParams.get('page')) - 1; if (dashboard_page_index in dashboard_pages) { return dashboard_pages[dashboard_page_index]; } } return this._dashboard_pages.keys().next().value; } getSelectedDashboardPage() { return this._selected_dashboard_page; } getDashboardPageIndex(dashboard_page) { if (this._is_kiosk_mode) { const dashboard_pages = [...this._dashboard_pages.keys()]; return dashboard_pages.indexOf(dashboard_page); } const tabs = [...this._tabs.getList().children]; const data = this._dashboard_pages.get(dashboard_page); return tabs.indexOf(data.tab); } save() { const data = { dashboardid: this._data.dashboardid ?? undefined, name: this._data.name, userid: this._data.userid ?? undefined, templateid: this._data.templateid ?? undefined, display_period: this._data.display_period, auto_start: this._data.auto_start, pages: [] }; let dashboard_pages = []; if (this._is_kiosk_mode) { for (const dashboard_page of this._dashboard_pages.keys()) { dashboard_pages.push(dashboard_page); } } else { for (const tab of this._tabs.getList().children) { dashboard_pages.push(this._tabs_dashboard_pages.get(tab)); } } for (const dashboard_page of dashboard_pages) { data.pages.push(dashboard_page.save()); } return data; } editProperties() { const properties = { template: this._data.templateid !== null ? 1 : undefined, userid: this._data.templateid === null ? this._data.userid : undefined, name: this._data.name, display_period: this._data.display_period, auto_start: this._data.auto_start }; PopUp('dashboard.properties.edit', properties, { dialogueid: 'dashboard_properties', dialogue_class: 'modal-popup-generic' }); } applyProperties() { const overlay = overlays_stack.getById('dashboard_properties'); const form = overlay.$dialogue.$body[0].querySelector('form'); const properties = getFormFields(form); overlay.setLoading(); const busy_condition = this._createBusyCondition(); return new Promise((resolve) => resolve(this._promiseApplyProperties(properties))) .then(() => { this._is_unsaved = true; overlayDialogueDestroy(overlay.dialogueid); this.fire(DASHBOARD_EVENT_APPLY_PROPERTIES); }) .catch((exception) => { for (const element of form.parentNode.children) { if (element.matches('.msg-good, .msg-bad, .msg-warning')) { element.parentNode.removeChild(element); } } let title, messages; if (typeof exception === 'object' && 'error' in exception) { title = exception.error.title; messages = exception.error.messages; } else { messages = [t('Failed to update dashboard properties.')]; } const message_box = makeMessageBox('bad', messages, title)[0]; form.parentNode.insertBefore(message_box, form); }) .finally(() => { overlay.unsetLoading(); this._deleteBusyCondition(busy_condition); }); } _promiseApplyProperties(properties) { properties.template = this._data.templateid !== null ? 1 : undefined; properties.name = properties.name.trim(); const curl = new Curl('zabbix.php'); curl.setArgument('action', 'dashboard.properties.check'); return fetch(curl.getUrl(), { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(properties) }) .then((response) => response.json()) .then((response) => { if ('error' in response) { throw {error: response.error}; } this._data.name = properties.name; this._data.userid = this._data.templateid === null ? properties.userid : null; this._data.display_period = properties.display_period; this._data.auto_start = properties.auto_start === '1' ? '1' : '0'; }); } editDashboardPageProperties(properties = {}) { properties.dashboard_display_period = this._data.display_period; PopUp('dashboard.page.properties.edit', properties, { dialogueid: 'dashboard_page_properties', dialogue_class: 'modal-popup-generic' }); } applyDashboardPageProperties() { const overlay = overlays_stack.getById('dashboard_page_properties'); const form = overlay.$dialogue.$body[0].querySelector('form'); const properties = getFormFields(form); overlay.setLoading(); const busy_condition = this._createBusyCondition(); return Promise.resolve() .then(() => this._promiseApplyDashboardPageProperties(properties, overlay.data)) .then(() => { this._is_unsaved = true; overlayDialogueDestroy(overlay.dialogueid); }) .catch((exception) => { for (const element of form.parentNode.children) { if (element.matches('.msg-good, .msg-bad, .msg-warning')) { element.parentNode.removeChild(element); } } let title, messages; if (typeof exception === 'object' && 'error' in exception) { title = exception.error.title; messages = exception.error.messages; } else { messages = [t('Failed to update dashboard page properties.')]; } const message_box = makeMessageBox('bad', messages, title)[0]; form.parentNode.insertBefore(message_box, form); }) .finally(() => { overlay.unsetLoading(); this._deleteBusyCondition(busy_condition); }); } _promiseApplyDashboardPageProperties(properties, data) { properties.name = properties.name.trim(); const curl = new Curl('zabbix.php'); curl.setArgument('action', 'dashboard.page.properties.check'); return fetch(curl.getUrl(), { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(properties) }) .then((response) => response.json()) .then((response) => { if ('error' in response) { throw {error: response.error}; } if (data.unique_id !== null) { const dashboard_page = this.getDashboardPage(data.unique_id); if (dashboard_page === null) { return; } const dashboard_page_name = dashboard_page.getName(); dashboard_page.setName(properties.name); dashboard_page.setDisplayPeriod(properties.display_period); if (properties.name !== dashboard_page_name && !this._is_kiosk_mode) { this._updateTab(dashboard_page); } } else { const dashboard_page = this.addDashboardPage({ dashboard_pageid: null, name: properties.name, display_period: properties.display_period, widgets: [] }); this._selectDashboardPage(dashboard_page); } }); } editWidgetProperties(properties = {}, {new_widget_pos = null} = {}) { 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 }, { dialogueid: 'widget_properties', dialogue_class: 'modal-widget-configuration' }); overlay.xhr.then(() => { const form = overlay.$dialogue.$body[0].querySelector('form'); const original_properties = overlay.data.original_properties; if (original_properties.unique_id === null) { this._new_widget_dashboard_page = this._selected_dashboard_page; this._new_widget_pos = new_widget_pos; const default_widget_size = this._widget_defaults[original_properties.type].size; if (this._new_widget_pos === null) { this._new_widget_pos_reserved = this._new_widget_dashboard_page.findFreePos(default_widget_size); } else { this._new_widget_pos_reserved = { ...default_widget_size, ...this._new_widget_pos }; this._new_widget_pos_reserved.width = Math.min(this._new_widget_pos_reserved.width, this._max_columns - this._new_widget_pos_reserved.x ); this._new_widget_pos_reserved.height = Math.min(this._new_widget_pos_reserved.height, this._max_rows - this._new_widget_pos_reserved.y ); this._new_widget_pos_reserved = this._new_widget_dashboard_page.accommodatePos( this._new_widget_pos_reserved ); } if (this._new_widget_pos_reserved === null) { for (const el of form.parentNode.children) { if (el.matches('.msg-warning')) { el.parentNode.removeChild(el); } } const message_box = makeMessageBox('warning', [], t('Cannot add widget: not enough free space on the dashboard.') )[0]; form.parentNode.insertBefore(message_box, form); overlay.$btn_submit[0].disabled = true; } } 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}); for (const fieldset of form.querySelectorAll(`fieldset.${CFormFieldsetCollapsible.ZBX_STYLE_COLLAPSIBLE}`)) { new CFormFieldsetCollapsible(fieldset); } try { new TabIndicators(); } catch (error) { } }); if (!this._is_edit_widget_properties_cancel_subscribed) { this._is_edit_widget_properties_cancel_subscribed = true; overlay.$dialogue[0].addEventListener('dialogue.close', this._events.editWidgetPropertiesCancel, {once: true} ); } } reloadWidgetProperties() { const overlay = overlays_stack.getById('widget_properties'); const form = overlay.$dialogue.$body[0].querySelector('form'); const fields = getFormFields(form); const properties = { type: fields.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 === overlay.data.original_properties.type) { properties.name = fields.name; properties.view_mode = fields.show_header == 1 ? ZBX_WIDGET_VIEW_MODE_NORMAL : ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER; delete fields.type; delete fields.name; delete fields.show_header; properties.fields = fields; } overlay.$dialogue[0].dispatchEvent(new CustomEvent('overlay.reload')); this.editWidgetProperties(properties, {new_widget_pos: this._new_widget_pos}); } applyWidgetProperties() { const overlay = overlays_stack.getById('widget_properties'); const form = overlay.$dialogue.$body[0].querySelector('form'); const fields = getFormFields(form); const templateid = this._data.templateid ?? undefined; const type = fields.type; const name = fields.name; const view_mode = fields.show_header == 1 ? ZBX_WIDGET_VIEW_MODE_NORMAL : ZBX_WIDGET_VIEW_MODE_HIDDEN_HEADER; delete fields.type; delete fields.name; delete fields.show_header; const dashboard_page = overlay.data.original_properties.dashboard_page_unique_id !== null ? this.getDashboardPage(overlay.data.original_properties.dashboard_page_unique_id) : null; const widget = dashboard_page !== null ? dashboard_page.getWidget(overlay.data.original_properties.unique_id) : null; const busy_condition = this._createBusyCondition(); return Promise.resolve() .then(() => this._promiseDashboardWidgetCheck({templateid, type, name, view_mode, fields})) .then(() => { this._is_unsaved = true; overlayDialogueDestroy(overlay.dialogueid); if (widget !== null && widget.getType() === type) { widget.updateProperties({name, view_mode, fields}); return; } 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 = { type, name, view_mode, fields, widgetid: null, pos: widget === null ? this._new_widget_pos_reserved : widget.getPos(), is_new: widget === null, rf_rate: 0, unique_id: this._createUniqueId() }; if (widget === null) { this._new_widget_dashboard_page.promiseScrollIntoView(widget_data.pos) .then(() => { this._new_widget_dashboard_page.addWidget(widget_data); this._new_widget_dashboard_page.resetWidgetPlaceholder(); this._new_widget_dashboard_page = null; this._new_widget_pos = null; this._new_widget_pos_reserved = null; }); } else { if (dashboard_page.getState() === DASHBOARD_PAGE_STATE_DESTROYED) { return; } dashboard_page.promiseScrollIntoView(widget_data.pos) .then(() => { dashboard_page.replaceWidget(widget, widget_data); dashboard_page.resetWidgetPlaceholder(); }); } }) .catch((exception) => { for (const element of form.parentNode.children) { if (element.matches('.msg-good, .msg-bad, .msg-warning')) { element.parentNode.removeChild(element); } } let title, messages; if (typeof exception === 'object' && 'error' in exception) { title = exception.error.title; messages = exception.error.messages; } else { messages = [t('Failed to update widget properties.')]; } const message_box = makeMessageBox('bad', messages, title)[0]; form.parentNode.insertBefore(message_box, form); }) .finally(() => { overlay.unsetLoading(); this._deleteBusyCondition(busy_condition); }); } _isEditingWidgetProperties() { return this._is_edit_widget_properties_cancel_subscribed; } _promiseDashboardWidgetCheck({templateid, type, name, view_mode, fields}) { 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}) }) .then((response) => response.json()) .then((response) => { if ('error' in response) { throw {error: response.error}; } }); } _cancelEditingWidgetProperties() { this._selected_dashboard_page.resetWidgetPlaceholder(); } _getDashboardPageActionsContextMenu(dashboard_page) { let menu = []; let menu_actions = []; if (this._can_edit_dashboards) { menu_actions.push({ label: t('Copy'), 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.')); } } }); } if (this._is_edit_mode) { menu_actions.push({ label: t('Delete'), disabled: this._dashboard_pages.size == 1, clickCallback: () => this.deleteDashboardPage(dashboard_page) }); } if (menu_actions.length > 0) { menu.push({ label: t('Actions'), items: menu_actions }); } if (this._is_editable) { menu.push({ items: [ { label: t('Properties'), clickCallback: () => { if (!this._is_edit_mode) { this.setEditMode({is_internal_call: true}); } this.editDashboardPageProperties({ name: dashboard_page.getName(), display_period: dashboard_page.getDisplayPeriod(), unique_id: dashboard_page.getUniqueId() }); } } ] }); } return menu; } // Dashboard view methods. _warn(warning) { this._clearWarnings(); 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 )); } _warnDashboardPageExhausted() { this._warn(t('Cannot add widget: not enough free space on the dashboard.')); } _clearWarnings() { if (this._warning_message_box !== null) { this._warning_message_box.remove(); this._warning_message_box = null; } } _isUserInteracting() { if (this._selected_dashboard_page.isUserInteracting()) { return true; } if (!this._is_kiosk_mode) { const has_aria_expanded = this._tabs .getList() .querySelector(`.${ZBX_STYLE_BTN_DASHBOARD_PAGE_PROPERTIES}[aria-expanded="true"]`) !== null; if (has_aria_expanded) { return true; } } return false; } _addTab(dashboard_page) { const tab = document.createElement('li'); const tab_contents = document.createElement('div'); const tab_contents_name = document.createElement('span'); tab.appendChild(tab_contents); tab_contents.appendChild(tab_contents_name); const data = this._dashboard_pages.get(dashboard_page); const name = dashboard_page.getName(); data.tab = tab; if (name !== '') { data.index = null; tab_contents_name.textContent = name; tab_contents_name.title = name; } else { let max_index = this._dashboard_pages.size - 1; for (const dashboard_page_data of this._dashboard_pages.values()) { if (dashboard_page_data.index !== null && dashboard_page_data.index > max_index) { max_index = dashboard_page_data.index; } } data.index = max_index + 1; const name = sprintf(t('Page %1$d'), data.index); tab_contents_name.textContent = name; tab_contents_name.title = name; } if (this._getDashboardPageActionsContextMenu(dashboard_page).length > 0) { const properties_button = document.createElement('button'); properties_button.type = 'button'; properties_button.title = t('Actions'); properties_button.setAttribute('aria-expanded', 'false'); properties_button.setAttribute('aria-haspopup', 'true'); properties_button.classList.add(ZBX_STYLE_BTN_ICON, ZBX_ICON_MORE, ZBX_STYLE_BTN_DASHBOARD_PAGE_PROPERTIES); tab_contents.append(properties_button); } this._tabs.insertItemBefore(tab); this._tabs_dashboard_pages.set(tab, dashboard_page); } _updateTab(dashboard_page) { const name = dashboard_page.getName(); const data = this._dashboard_pages.get(dashboard_page); const tab_contents_name = data.tab.firstElementChild.firstElementChild; data.index = null; if (name !== '') { tab_contents_name.textContent = name; tab_contents_name.title = name; } else { const tab_index = [...this._tabs.getList().children].indexOf(data.tab) + 1; let max_index = this._dashboard_pages.size - 1; let is_tab_index_available = true; for (const dashboard_page_data of this._dashboard_pages.values()) { if (dashboard_page_data.index !== null) { if (dashboard_page_data.index === tab_index) { is_tab_index_available = false; } if (dashboard_page_data.index > max_index) { max_index = dashboard_page_data.index; } } } data.index = is_tab_index_available ? tab_index : max_index + 1; const name = sprintf(t('Page %1$d'), data.index); tab_contents_name.textContent = name; tab_contents_name.title = name; } } _deleteTab(dashboard_page) { const data = this._dashboard_pages.get(dashboard_page); this._tabs.removeItem(data.tab); this._tabs_dashboard_pages.delete(data.tab); } _selectTab(dashboard_page) { this._tabs.getList().querySelectorAll(`.${ZBX_STYLE_DASHBOARD_SELECTED_TAB}`).forEach((el) => { el.classList.remove(ZBX_STYLE_DASHBOARD_SELECTED_TAB); }) const data = this._dashboard_pages.get(dashboard_page); data.tab.firstElementChild.classList.add(ZBX_STYLE_DASHBOARD_SELECTED_TAB); this._updateNavigationButtons(dashboard_page); this._tabs.scrollItemIntoView(data.tab); } _updateNavigationButtons(dashboard_page = null) { this._containers.navigation.classList.toggle(ZBX_STYLE_DASHBOARD_NAVIGATION_IS_SCROLLABLE, this._tabs.isScrollable() ); if (dashboard_page !== null) { const tab = this._dashboard_pages.get(dashboard_page).tab; this._buttons.previous_page.disabled = tab.previousElementSibling === null; this._buttons.next_page.disabled = tab.nextElementSibling === null; } } _reserveHeaderLines(num_lines) { this._reserve_header_lines = num_lines; if (this._reserve_header_lines_timeout_id !== null) { clearTimeout(this._reserve_header_lines_timeout_id); this._reserve_header_lines_timeout_id = null; } let old_num_lines = 0; for (let i = 2; i > 0; i--) { if (this._containers.grid.classList.contains(`reserve-header-lines-${i}`)) { old_num_lines = i; break; } } if (num_lines > old_num_lines) { if (old_num_lines > 0) { this._containers.grid.classList.remove(`reserve-header-lines-${old_num_lines}`); } this._containers.grid.classList.add(`reserve-header-lines-${num_lines}`); } else if (num_lines < old_num_lines) { this._reserve_header_lines_timeout_id = setTimeout(() => { this._reserve_header_lines_timeout_id = null; this._containers.grid.classList.remove(`reserve-header-lines-${old_num_lines}`); if (num_lines > 0) { this._containers.grid.classList.add(`reserve-header-lines-${num_lines}`); } }, this._header_lines_steady_period); } } _keepSteadyHeaderLines() { if (this._reserve_header_lines_timeout_id !== null) { this._reserveHeaderLines(this._reserve_header_lines); } } _resetHeaderLines() { if (this._reserve_header_lines_timeout_id !== null) { clearTimeout(this._reserve_header_lines_timeout_id); this._reserve_header_lines_timeout_id = null; } this._containers.grid.classList.remove('reserve-header-lines-1', 'reserve-header-lines-2'); } _createUniqueId() { return 'U' + (this._unique_id_index++).toString(36).toUpperCase().padStart(6, '0'); } _createReference({used_references = null} = {}) { if (used_references === null) { used_references = this._getUsedReferences(); } let reference; do { reference = ''; for (let i = 0; i < 5; i++) { reference += String.fromCharCode(65 + Math.floor(Math.random() * 26)); } } while (used_references.has(reference)); return reference; } _getUsedReferences() { const used_references = new Set(); for (const dashboard_page of this._dashboard_pages.keys()) { for (const widget of dashboard_page.getWidgets()) { const fields = widget.getFields(); if (widget.constructor.hasReferenceField()) { used_references.add(fields.reference); } for (const reference_field of widget.constructor.getForeignReferenceFields()) { used_references.add(fields[reference_field]); } } } return used_references; } // Internal events management methods. _registerEvents() { let wrapper_scrollbar_width = 0; let user_interaction_animation_frame = null; this._events = { dashboardPageEdit: () => { this.setEditMode({is_internal_call: true}); }, 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 menu = [ { label: t('Actions'), items: [ { label: t('Add widget'), clickCallback: () => this.editWidgetProperties({}, {new_widget_pos}) }, { label: t('Paste widget'), clickCallback: () => this.pasteWidget(new_widget_data, {new_widget_pos}) } ] } ]; const placeholder = e.detail.placeholder; const placeholder_event = new jQuery.Event(e.detail.mouse_event); placeholder_event.target = placeholder; jQuery(placeholder).menuPopup(menu, placeholder_event, { closeCallback: () => { if (!this._isEditingWidgetProperties()) { dashboard_page.resetWidgetPlaceholder(); } } }); } else { this.editWidgetProperties({}, {new_widget_pos}); if (!this._isEditingWidgetProperties()) { dashboard_page.resetWidgetPlaceholder(); } } }, dashboardPageWidgetAddNew: () => { this.editWidgetProperties(); }, dashboardPageWidgetDelete: () => { this._clearWarnings(); }, dashboardPageWidgetPosition: () => { this._clearWarnings(); }, dashboardPageWidgetActions: (e) => { const menu = e.detail.widget.getActionsContextMenu({ can_paste_widget: this.getStoredWidgetDataCopy() !== null }); jQuery(e.detail.mouse_event.target).menuPopup(menu, new jQuery.Event(e.detail.mouse_event)); }, dashboardPageWidgetEdit: (e) => { const dashboard_page = e.detail.target; const widget = e.detail.widget; this.editWidgetProperties({ type: widget.getType(), name: widget.getName(), view_mode: widget.getViewMode(), fields: widget.getFields(), unique_id: widget.getUniqueId(), dashboard_page_unique_id: dashboard_page.getUniqueId() }); }, dashboardPageWidgetCopy: (e) => { const widget = e.detail.widget; this._storeWidgetDataCopy(widget.getDataCopy({is_single_copy: true})); }, dashboardPageWidgetPaste: (e) => { const widget = e.detail.widget; this.pasteWidget(this.getStoredWidgetDataCopy(), {widget}); }, dashboardPageAnnounceWidgets: () => { this._announceWidgets(); }, dashboardPageReserveHeaderLines: (e) => { this._reserveHeaderLines(e.detail.num_lines); }, gridResize: () => { const wrapper = document.querySelector('.wrapper'); if (wrapper.offsetWidth > wrapper.clientWidth) { wrapper_scrollbar_width = wrapper.offsetWidth - wrapper.clientWidth; this._buttons.next_page.style.marginRight = '0'; } else { this._buttons.next_page.style.marginRight = `${wrapper_scrollbar_width}px`; } }, tabsResize: () => { this._updateNavigationButtons(); }, tabsDragStart: () => { this._selected_dashboard_page.blockInteraction(); }, tabsDragEnd: () => { this._selected_dashboard_page.unblockInteraction(); this._updateNavigationButtons(); }, tabsSort: () => { this._setInitialDashboardPage(this._selected_dashboard_page); this._is_unsaved = true; }, tabsClick: (e) => { const tab = e.target.closest(`.${ZBX_STYLE_SORTABLE_ITEM}`); if (tab !== null && tab.parentNode.classList.contains(ZBX_STYLE_SORTABLE_LIST)) { const dashboard_page = this._tabs_dashboard_pages.get(tab); if (dashboard_page !== this._selected_dashboard_page) { this._selectDashboardPage(dashboard_page, {is_async: true}); } else if (e.target.classList.contains(ZBX_STYLE_BTN_DASHBOARD_PAGE_PROPERTIES)) { jQuery(e.target).menuPopup(this._getDashboardPageActionsContextMenu(dashboard_page), new jQuery.Event(e) ); } } }, tabsKeyDown: (e) => { if (e.key === 'Enter') { const tab = e.target.closest(`.${ZBX_STYLE_SORTABLE_ITEM}`); if (tab !== null) { const dashboard_page = this._tabs_dashboard_pages.get(tab); if (dashboard_page !== this._selected_dashboard_page) { this._selectDashboardPage(dashboard_page, {is_async: true}); } else if (e.target.classList.contains(ZBX_STYLE_BTN_DASHBOARD_PAGE_PROPERTIES)) { jQuery(e.target).menuPopup(this._getDashboardPageActionsContextMenu(dashboard_page), new jQuery.Event(e) ); } } } }, tabsPreviousPageClick: () => { const tab = this._dashboard_pages.get(this._selected_dashboard_page).tab; this._selectDashboardPage(this._tabs_dashboard_pages.get(tab.previousElementSibling), {is_async: true}); }, tabsNextPageClick: () => { const tab = this._dashboard_pages.get(this._selected_dashboard_page).tab; this._selectDashboardPage(this._tabs_dashboard_pages.get(tab.nextElementSibling), {is_async: true}); }, slideshowToggle: () => { if (this._is_edit_mode) { return; } if (this._isSlideshowRunning()) { this._stopSlideshow(); } else { this._startSlideshow(); } }, userInteract: () => { if (user_interaction_animation_frame !== null) { cancelAnimationFrame(user_interaction_animation_frame); } user_interaction_animation_frame = requestAnimationFrame(() => { user_interaction_animation_frame = null; if (this._is_kiosk_mode) { this._keepSteadyHeaderLines(); } if (!this._is_edit_mode) { this._keepSteadyConfigurationChecker(); if (this._isSlideshowRunning()) { this._keepSteadySlideshow(); } } }); }, kioskModePreviousPageClick: () => { const dashboard_pages = [...this._dashboard_pages.keys()]; const dashboard_page_index = dashboard_pages.indexOf(this._selected_dashboard_page); this._selectDashboardPage( dashboard_pages[dashboard_page_index > 0 ? dashboard_page_index - 1 : dashboard_pages.length - 1] ); }, kioskModeNextPageClick: () => { const dashboard_pages = [...this._dashboard_pages.keys()]; const dashboard_page_index = dashboard_pages.indexOf(this._selected_dashboard_page); this._selectDashboardPage( dashboard_pages[dashboard_page_index < dashboard_pages.length - 1 ? dashboard_page_index + 1 : 0] ); }, timeSelectorRangeUpdate: (e, time_period) => { this._time_period = { from: time_period.from, from_ts: time_period.from_ts, to: time_period.to, to_ts: time_period.to_ts }; for (const dashboard_page of this._dashboard_pages.keys()) { dashboard_page.setTimePeriod(this._time_period); } }, editWidgetPropertiesCancel: () => { this._cancelEditingWidgetProperties(); this._is_edit_widget_properties_cancel_subscribed = false; } }; } _activateEvents() { if (!this._is_kiosk_mode) { new ResizeObserver(this._events.gridResize).observe(this._containers.grid); new ResizeObserver(this._events.tabsResize).observe(this._containers.navigation_tabs); this._tabs.on(SORTABLE_EVENT_DRAG_START, this._events.tabsDragStart); this._tabs.on(SORTABLE_EVENT_DRAG_END, this._events.tabsDragEnd); this._tabs.on(SORTABLE_EVENT_SORT, this._events.tabsSort); this._containers.navigation_tabs.addEventListener('click', this._events.tabsClick); this._containers.navigation_tabs.addEventListener('keydown', this._events.tabsKeyDown); this._buttons.previous_page.addEventListener('click', this._events.tabsPreviousPageClick); this._buttons.next_page.addEventListener('click', this._events.tabsNextPageClick); } if (this._buttons.slideshow !== null && !this._is_edit_mode && this._dashboard_pages.size > 1) { this._buttons.slideshow.addEventListener('click', this._events.slideshowToggle); } for (const event_name of ['mousemove', 'mousedown', 'keydown', 'wheel']) { window.addEventListener(event_name, this._events.userInteract); } if (this._is_kiosk_mode) { if (this._buttons.previous_page !== null) { this._buttons.previous_page.addEventListener('click', this._events.kioskModePreviousPageClick); } if (this._buttons.next_page !== null) { this._buttons.next_page.addEventListener('click', this._events.kioskModeNextPageClick); } } if (this._time_period !== null) { jQuery.subscribe('timeselector.rangeupdate', this._events.timeSelectorRangeUpdate); } } /** * Attach event listener to dashboard events. * * @param {string} type * @param {function} listener * @param {Object|false} options * * @returns {CDashboard} */ on(type, listener, options = false) { this._target.addEventListener(type, listener, options); return this; } /** * Detach event listener from dashboard events. * * @param {string} type * @param {function} listener * @param {Object|false} options * * @returns {CDashboard} */ off(type, listener, options = false) { this._target.removeEventListener(type, listener, options); return this; } /** * Dispatch dashboard event. * * @param {string} type * @param {Object} detail * @param {Object} options * * @returns {boolean} */ fire(type, detail = {}, options = {}) { return this._target.dispatchEvent(new CustomEvent(type, {...options, detail: {target: this, ...detail}})); } }