diff options
author | Alexander Shubin <aleksandrs.subins@zabbix.com> | 2022-07-12 08:43:22 +0300 |
---|---|---|
committer | Alexander Shubin <aleksandrs.subins@zabbix.com> | 2022-07-12 08:43:22 +0300 |
commit | f5d9f3f7f582f547d0e703a35c91ec88e5b02ff6 (patch) | |
tree | 6f370c048cfd4ae7cbe2a313718e19537f533a8a /ui | |
parent | bf2ee5b9cc17afa80f960e2931f3cc4251bbd98a (diff) | |
parent | b3b292fb126dd85d451db9d1555b524c767b25d8 (diff) |
.......... [ZBX-20908] updated to latest release/6.0; no conflicts
Diffstat (limited to 'ui')
28 files changed, 985 insertions, 362 deletions
diff --git a/ui/app/controllers/CControllerHostEdit.php b/ui/app/controllers/CControllerHostEdit.php index 51b2c6e58c0..886545c4a22 100644 --- a/ui/app/controllers/CControllerHostEdit.php +++ b/ui/app/controllers/CControllerHostEdit.php @@ -133,7 +133,8 @@ class CControllerHostEdit extends CController { if ($this->hasInput('full_clone') || $this->hasInput('clone')) { $clone_hostid = $this->getInput('hostid'); $this->host = ['hostid' => null]; - } else { + } + else { $hosts = API::Host()->get([ 'output' => ['hostid', 'host', 'name', 'status', 'description', 'proxy_hostid', 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username', 'ipmi_password', 'tls_connect', 'tls_accept', 'tls_issuer', @@ -352,7 +353,7 @@ class CControllerHostEdit extends CController { */ protected function extendDiscoveryRule(?array &$editable_discovery_rule): void { $editable_discovery_rule = $this->host['discoveryRule'] - ? API::DiscoveryRule([ + ? API::DiscoveryRule()->get([ 'output' => [], 'itemids' => array_column($this->host['discoveryRule'], 'itemid'), 'editable' => true, diff --git a/ui/app/controllers/CControllerWidgetGraphView.php b/ui/app/controllers/CControllerWidgetGraphView.php index 82ac42643e9..ea69a05caa7 100644 --- a/ui/app/controllers/CControllerWidgetGraphView.php +++ b/ui/app/controllers/CControllerWidgetGraphView.php @@ -92,7 +92,7 @@ class CControllerWidgetGraphView extends CControllerWidget { $is_dynamic_item = ($is_template_dashboard || $fields['dynamic'] == WIDGET_DYNAMIC_ITEM); // Replace graph item by particular host item if dynamic items are used. - if ($is_dynamic_item && $dynamic_hostid && $resourceid) { + if ($is_dynamic_item && $dynamic_hostid != 0 && $resourceid) { // Find same simple-graph item in selected $dynamic_hostid host. if ($fields['source_type'] == ZBX_WIDGET_FIELD_RESOURCE_SIMPLE_GRAPH) { $src_items = API::Item()->get([ @@ -218,7 +218,7 @@ class CControllerWidgetGraphView extends CControllerWidget { // get graph, used below $graph = API::Graph()->get([ 'output' => API_OUTPUT_EXTEND, - 'selectHosts' => ['name'], + 'selectHosts' => ['hostid', 'name'], 'graphids' => $resourceid, 'expandName' => true ]); @@ -267,7 +267,7 @@ class CControllerWidgetGraphView extends CControllerWidget { ? $graph['hosts'][0]['name'].NAME_DELIMITER.$graph['name'] : $graph['name']; - if ($is_dynamic_item && $dynamic_hostid && $resourceid) { + if ($is_dynamic_item && $dynamic_hostid != 0 && $resourceid) { if ($graph['graphtype'] == GRAPH_TYPE_PIE || $graph['graphtype'] == GRAPH_TYPE_EXPLODED) { $graph_src = (new CUrl('chart7.php')) ->setArgument('name', $host['name'].NAME_DELIMITER.$graph['name']) @@ -341,30 +341,31 @@ class CControllerWidgetGraphView extends CControllerWidget { } else { if ($fields['source_type'] == ZBX_WIDGET_FIELD_RESOURCE_GRAPH) { - if ($is_dynamic_item && $dynamic_hostid) { - $template_graphs = API::Graph()->get([ - 'output' => ['name'], - 'graphids' => [$resourceid] - ]); - - $resourceid = null; - - if ($template_graphs) { - $host_graphs = API::Graph()->get([ - 'output' => ['graphid'], - 'hostids' => [$dynamic_hostid], - 'filter' => [ - 'name' => $template_graphs[0]['name'] - ] - ]); - - if ($host_graphs) { - $resourceid = $host_graphs[0]['graphid']; - } - } + $has_host_graph = $is_dynamic_item && $dynamic_hostid != 0 + ? (bool) API::Graph()->get([ + 'output' => [], + 'hostids' => [$dynamic_hostid], + 'filter' => [ + 'name' => $graph['name'] + ] + ]) + : true; + + if ($has_host_graph) { + $graph_url = $this->checkAccess(CRoleHelper::UI_MONITORING_HOSTS) + ? (new CUrl('zabbix.php')) + ->setArgument('action', 'charts.view') + ->setArgument('filter_hostids', [$graph['hosts'][0]['hostid']]) + ->setArgument('filter_name', $graph['name']) + ->setArgument('filter_show', GRAPH_FILTER_HOST) + ->setArgument('filter_set', '1') + ->setArgument('from', '') + ->setArgument('to', '') + : null; + } + else { + $graph_url = null; } - - $graph_url = null; } else { $graph_url = $this->checkAccess(CRoleHelper::UI_MONITORING_LATEST_DATA) diff --git a/ui/app/partials/configuration.host.edit.html.php b/ui/app/partials/configuration.host.edit.html.php index 19f5cc1b093..735960f80bb 100644 --- a/ui/app/partials/configuration.host.edit.html.php +++ b/ui/app/partials/configuration.host.edit.html.php @@ -556,6 +556,5 @@ if (array_key_exists('buttons', $data)) { } $host_form - ->addItem($data['warning']) ->addItem($tabs) ->show(); diff --git a/ui/app/partials/monitoring.problem.filter.php b/ui/app/partials/monitoring.problem.filter.php index e0627962b8c..24d36d8bf48 100644 --- a/ui/app/partials/monitoring.problem.filter.php +++ b/ui/app/partials/monitoring.problem.filter.php @@ -118,7 +118,6 @@ $left_column ->setChecked($data['age_state'] == 1) ->setUncheckedValue(0) ->setId('age_state_#{uniqid}'), - (new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN), $filter_age, (new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN), _('days') diff --git a/ui/app/views/administration.user.token.edit.php b/ui/app/views/administration.user.token.edit.php index 51682ff5314..12f7cbc3c70 100644 --- a/ui/app/views/administration.user.token.edit.php +++ b/ui/app/views/administration.user.token.edit.php @@ -64,7 +64,8 @@ $token_from_list = (new CFormList()) (new CDateSelector('expires_at', $data['expires_at'])) ->setDateFormat(ZBX_FULL_DATE_TIME) ->setPlaceholder(_('YYYY-MM-DD hh:mm:ss')) - ->setAriaRequired() + ->setAriaRequired(), + 'expires-at-row' ) ->addRow(_('Enabled'), (new CCheckBox('status', ZBX_AUTH_TOKEN_ENABLED)) @@ -72,7 +73,6 @@ $token_from_list = (new CFormList()) ->setUncheckedValue(ZBX_AUTH_TOKEN_DISABLED) ); - $token_view = (new CTabView())->addTab('token', '', $token_from_list); $cancel_button = (new CRedirectButton(_('Cancel'), (new CUrl('zabbix.php')) ->setArgument('action', 'user.token.list') diff --git a/ui/app/views/js/monitoring.charts.view.js.php b/ui/app/views/js/monitoring.charts.view.js.php index 878143611a6..7e1ec9d4b96 100644 --- a/ui/app/views/js/monitoring.charts.view.js.php +++ b/ui/app/views/js/monitoring.charts.view.js.php @@ -61,7 +61,7 @@ this._app = new ChartList( $('#charts'), this._data.timeline, this._data.config, this._container); this._app.setCharts(this._data.charts); - this._app.onResize(); + this._app.refresh(); this._resize_observer = new ResizeObserver(this._app.onResize.bind(this._app)); this._resize_observer.observe(this._container); @@ -246,27 +246,6 @@ }; /** - * Start or pause timeout based Chart refresh. - * - * @param {number} seconds Seconds to wait before reschedule. Zero seconds will pause schedule. - * @param {number} delay_loading (optional) Add "loading indicator" only when request exceeds delay. - */ - Chart.prototype.scheduleRefresh = function(seconds, delay_loading) { - if (this._timeoutid) { - clearTimeout(this._timeoutid); - } - - if (!seconds) { - return; - } - - this.refresh(delay_loading) - .finally(_ => { - this._timeoutid = setTimeout(() => this.scheduleRefresh(seconds, 0), seconds * 1000); - }); - }; - - /** * @param {jQuery} $el A container where charts are maintained. * @param {object} timeline Time control object. * @param {object} config @@ -372,10 +351,6 @@ clearTimeout(this._timeoutid); } - for (const chart of this.charts) { - chart.scheduleRefresh(0); - } - this.updateListAndCharts(delay_loading) .finally(_ => { if (refresh_interval) { @@ -430,7 +405,6 @@ if (this._prev_width === undefined) { this._prev_width = width; - this.updateCharts(); return; } diff --git a/ui/app/views/js/monitoring.problem.view.js.php b/ui/app/views/js/monitoring.problem.view.js.php index ed6cc5fa9a1..02756407b50 100644 --- a/ui/app/views/js/monitoring.problem.view.js.php +++ b/ui/app/views/js/monitoring.problem.view.js.php @@ -157,7 +157,7 @@ refreshResults() { const url = new Curl(); const screen = window.flickerfreeScreen.screens['problem']; - const data = $.extend(this.filter_defaults, this.global_timerange, url.getArgumentsObject()); + const data = Object.assign({}, this.filter_defaults, this.global_timerange, url.getArgumentsObject()); data.inventory = data.inventory ? data.inventory.filter(inventory => 'value' in inventory && inventory.value !== '') diff --git a/ui/app/views/js/monitoring.widget.navtreeitem.edit.js.php b/ui/app/views/js/monitoring.widget.navtreeitem.edit.js.php new file mode 100644 index 00000000000..fd68a9c09a0 --- /dev/null +++ b/ui/app/views/js/monitoring.widget.navtreeitem.edit.js.php @@ -0,0 +1,48 @@ +<?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. +**/ + + +/** + * @var CView $this + */ +?> + +window.navtreeitem_edit_popup = new class { + init() { + jQuery('#sysmapname').on('change', (e) => { + const name_input = document.getElementById('name'); + + if (name_input.value === '') { + name_input.value = e.target.value; + } + }); + + document.getElementById('select').addEventListener('click', () => { + return PopUp('popup.generic', { + srctbl: 'sysmaps', + srcfld1: 'sysmapid', + srcfld2: 'name', + dstfrm: 'widget_dialogue_form', + dstfld1: 'sysmapid', + dstfld2: 'sysmapname' + }, {dialogue_class: 'modal-popup-generic'}); + }); + } +}; diff --git a/ui/app/views/js/popup.host.edit.js.php b/ui/app/views/js/popup.host.edit.js.php index 8557d0bf86c..c22ff8ab198 100644 --- a/ui/app/views/js/popup.host.edit.js.php +++ b/ui/app/views/js/popup.host.edit.js.php @@ -29,7 +29,7 @@ window.host_edit_popup = { dialogue: null, form: null, - init({popup_url, form_name, host_interfaces, host_is_discovered}) { + init({popup_url, form_name, host_interfaces, host_is_discovered, warning}) { this.overlay = overlays_stack.getById('host_edit'); this.dialogue = this.overlay.$dialogue[0]; this.form = this.overlay.$dialogue.$body[0].querySelector('form'); @@ -39,6 +39,12 @@ window.host_edit_popup = { history.replaceState({}, '', popup_url); host_edit.init({form_name, host_interfaces, host_is_discovered}); + + if (warning !== null) { + const message_box = makeMessageBox('warning', warning, null, true, false)[0]; + + this.form.parentNode.insertBefore(message_box, this.form); + } }, addEventListeners() { diff --git a/ui/app/views/monitoring.widget.navtreeitem.edit.php b/ui/app/views/monitoring.widget.navtreeitem.edit.php index accedaf91f9..1794d6e1ef1 100644 --- a/ui/app/views/monitoring.widget.navtreeitem.edit.php +++ b/ui/app/views/monitoring.widget.navtreeitem.edit.php @@ -39,24 +39,9 @@ $form_list = (new CFormList()) ) ->addRow(_('Linked map'), [ new CVar('sysmapid', $data['sysmap']['sysmapid']), - (new CTextBox('sysmapname', $data['sysmap']['name'], true)) - ->setAttribute('onChange', - 'javascript: if(jQuery("#'.$form->getName().' input[type=text]:first").val() === ""){'. - 'jQuery("#widget-dialogue-form input[type=text]:first").val(this.value);}') - ->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH), + (new CTextBox('sysmapname', $data['sysmap']['name'], true))->setWidth(ZBX_TEXTAREA_MEDIUM_WIDTH), (new CDiv())->addClass(ZBX_STYLE_FORM_INPUT_MARGIN), - (new CButton('select', _('Select'))) - ->addClass(ZBX_STYLE_BTN_GREY) - ->onClick( - 'return PopUp("popup.generic", '.json_encode([ - 'srctbl' => 'sysmaps', - 'srcfld1' => 'sysmapid', - 'srcfld2' => 'name', - 'dstfrm' => $form->getName(), - 'dstfld1' => 'sysmapid', - 'dstfld2' => 'sysmapname' - ]).', {dialogue_class: "modal-popup-generic"});' - ) + (new CButton('select', _('Select')))->addClass(ZBX_STYLE_BTN_GREY) ]); if ($data['depth'] >= WIDGET_NAVIGATION_TREE_MAX_DEPTH) { @@ -69,10 +54,13 @@ else { ]); } -$form->addItem($form_list); +$form + ->addItem($form_list) + ->addItem((new CScriptTag('navtreeitem_edit_popup.init();'))->setOnDocumentReady()); $output = [ - 'body' => $form->toString() + 'body' => $form->toString(), + 'script_inline' => $this->readJsFile('monitoring.widget.navtreeitem.edit.js.php') ]; if (($messages = getMessages()) !== null) { diff --git a/ui/app/views/popup.host.edit.php b/ui/app/views/popup.host.edit.php index 7923458b968..64ec911da05 100644 --- a/ui/app/views/popup.host.edit.php +++ b/ui/app/views/popup.host.edit.php @@ -89,10 +89,6 @@ else { ]; } -if ($data['warning']) { - $data['warning'] = makeMessageBox(ZBX_STYLE_MSG_WARNING, [['message' => $data['warning']]]); -} - $output = [ 'header' => ($data['hostid'] == 0) ? _('New host') : _('Host'), 'body' => (new CPartial('configuration.host.edit.html', $data))->getOutput(), @@ -102,7 +98,8 @@ $output = [ 'popup_url' => $popup_url->getUrl(), 'form_name' => $data['form_name'], 'host_interfaces' => $data['host']['interfaces'], - 'host_is_discovered' => ($data['host']['flags'] == ZBX_FLAG_DISCOVERY_CREATED) + 'host_is_discovered' => ($data['host']['flags'] == ZBX_FLAG_DISCOVERY_CREATED), + 'warning' => $data['warning'] ]).');', 'buttons' => $buttons ]; diff --git a/ui/assets/styles/blue-theme.css b/ui/assets/styles/blue-theme.css index 4c21e63bb6d..82ad95e0dc5 100644 --- a/ui/assets/styles/blue-theme.css +++ b/ui/assets/styles/blue-theme.css @@ -2622,8 +2622,9 @@ z-select, color: #1f2c33; text-overflow: ellipsis; word-break: break-word; - white-space: pre-wrap; - user-select: none; } + user-select: none; + white-space: nowrap; + overflow: hidden; } z-select .list li.hover, .z-select .list li.hover { background-color: #e8f5ff; } @@ -2896,7 +2897,6 @@ form.is-loading { box-shadow: inset 0 -1px 0 0 rgba(31, 44, 51, 0.1); color: #1f2c33; height: 22px; - line-height: 1; vertical-align: middle; white-space: nowrap; } .list-table.compact-view td [class*='icon-'] { @@ -3639,19 +3639,31 @@ select { .checkbox-radio { position: absolute !important; top: auto !important; + z-index: 1; width: 16px !important; height: 16px !important; - opacity: 0; - z-index: 1; } + opacity: 0; } + .checkbox-radio + label span { + position: relative; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 4px; + vertical-align: text-bottom; + background-color: #ffffff; + border: 1px solid #acbbc2; } + .checkbox-radio + label span:active { + border-color: #acbbc2; } + .checkbox-radio + label.label-pos-left span { + margin-left: 4px; + margin-right: 0; } .checkbox-radio:checked[readonly] + label span { transition: none; } .checkbox-radio:checked[readonly] + label span::after { border-color: #acbbc2 !important; } - .checkbox-radio[readonly], - .checkbox-radio[readonly] + label { + .checkbox-radio[readonly], .checkbox-radio[readonly] + label { pointer-events: none; } - .checkbox-radio[readonly] span, - .checkbox-radio[readonly] + label span { + .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span { transition: none; } .checkbox-radio[type="checkbox"] + label span { border-radius: 2px; } @@ -3682,20 +3694,6 @@ select { border-radius: 50%; } .checkbox-radio[type="radio"]:checked[disabled] + label span::after { background-color: #acbbc2 !important; } - .checkbox-radio + label span { - position: relative; - display: inline-block; - width: 14px; - height: 14px; - margin: -4px 4px 0 0; - bottom: -3px; - border: 1px solid #acbbc2; - background-color: #ffffff; } - .checkbox-radio + label span:active { - border-color: #acbbc2; } - .checkbox-radio + label.label-pos-left span { - margin-left: 6px; - margin-right: 1px; } .checkbox-radio:focus + label span { border-color: #02659f; } .checkbox-radio:checked:focus + label span { @@ -3707,7 +3705,7 @@ select { .checkbox-radio[disabled] + label span { transition: none; } -input[type="checkbox"], input[type="radio"] { +input[type="radio"] { margin: 0; padding: 0; border: 0; @@ -4211,8 +4209,7 @@ button[disabled], button[disabled]:hover, button[disabled]:active { .radio-list-control input[type="radio"][disabled] + label, z-select[disabled] button, .z-select[disabled] button, z-select[readonly] button, -.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, -.checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { +.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { color: #acbbc2; background-color: #ebebeb !important; border-color: #ccd5d9; } @@ -5861,10 +5858,7 @@ svg { .checkbox-list li { overflow: hidden; text-overflow: ellipsis; - line-height: 20px; } - .checkbox-list li label { - line-height: 16px; - vertical-align: bottom; } + line-height: 24px; } .columns-wrapper { display: flex; @@ -5926,7 +5920,8 @@ svg { .preprocessing-list { display: block; max-width: 930px; - min-width: 800px; } + min-width: 800px; + position: relative; } .preprocessing-list > li { display: block; position: relative; } @@ -6400,47 +6395,44 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control { position: relative; - display: inline-block; - vertical-align: middle; } + display: inline-flex; } .range-control input[type=range] { + width: calc(100% + 10px); + height: 20px; + margin: 2px -5px; + padding: 0; + vertical-align: top; + opacity: 0; cursor: pointer; -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ -moz-appearance: none; /* Hides the slider so that custom slider can be made */ - width: calc(100% + 10px); - opacity: 0; - vertical-align: middle; - margin: 0 -5px; - padding: 0; - height: 20px; /* Special styling for WebKit/Blink */ } .range-control input[type=range]:focus { outline: none; } .range-control input[type=range]::-webkit-slider-thumb, .range-control input[type=range]::-moz-range-thumb { - margin-top: 1px; - /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ height: 10px; width: 10px; + margin-top: 1px; + /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ opacity: 0; } .range-control input[type=range]::-webkit-slider-runnable-track { height: 10px; opacity: 0; } .range-control input[type=text] { - width: 31px; - float: right; } + width: 31px; } .range-control > div { position: relative; width: calc(100% - 10px - 31px); - margin: 2px 10px 0 0; - float: left; } + margin-right: 10px; } .range-control .range-control-track, .range-control .range-control-progress { position: absolute; top: 50%; - margin-top: -1px; left: 0; height: 2px; + margin-top: -1px; cursor: pointer; } .range-control .range-control-track { width: 100%; @@ -6450,12 +6442,12 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control .range-control-thumb { position: absolute; top: 50%; - margin-top: -5px; - margin-left: -5px; width: 10px; height: 10px; - border-radius: 50%; - background: #0275b8; } + margin-top: -5px; + margin-left: -5px; + background: #0275b8; + border-radius: 50%; } .range-control.range-control-focus .range-control-thumb { margin-top: -6.66667px; margin-left: -6.66667px; @@ -6472,15 +6464,15 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu background: #c0c0c0; } .range-control datalist { position: absolute; + top: 50%; display: flex; width: 100%; - top: 50%; margin-top: -1px; } .range-control datalist option { - padding: 0; - font-size: 0; flex: 1 0 0; height: 2px; + padding: 0; + font-size: 0; border-left: 1px solid #ebeef0; } .graph-legend { @@ -6980,9 +6972,9 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), background-position: -117px -506px !important; } .geomap-filter { - border: 2px solid rgba(223, 228, 231, 0.4); + border: 1px solid #ccd5d9; background-color: #ffffff; - padding: 10px 30px 5px 10px; + padding: 5px 20px 5px 10px; position: absolute; margin-right: -2px; margin-top: 5px; @@ -6990,7 +6982,7 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), right: 0; } .geomap-filter.collapsed { - display: table; } + display: block; } .geomap-filter li { width: calc(100% + 20px); } diff --git a/ui/assets/styles/dark-theme.css b/ui/assets/styles/dark-theme.css index 4763e28b245..bd234e3177e 100644 --- a/ui/assets/styles/dark-theme.css +++ b/ui/assets/styles/dark-theme.css @@ -2635,8 +2635,9 @@ z-select, color: #f2f2f2; text-overflow: ellipsis; word-break: break-word; - white-space: pre-wrap; - user-select: none; } + user-select: none; + white-space: nowrap; + overflow: hidden; } z-select .list li.hover, .z-select .list li.hover { background-color: #454545; } @@ -2907,7 +2908,6 @@ form.is-loading { box-shadow: inset 0 -1px 0 0 rgba(242, 242, 242, 0.1); color: #f2f2f2; height: 22px; - line-height: 1; vertical-align: middle; white-space: nowrap; } .list-table.compact-view td [class*='icon-'] { @@ -3650,19 +3650,31 @@ select { .checkbox-radio { position: absolute !important; top: auto !important; + z-index: 1; width: 16px !important; height: 16px !important; - opacity: 0; - z-index: 1; } + opacity: 0; } + .checkbox-radio + label span { + position: relative; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 4px; + vertical-align: text-bottom; + background-color: #383838; + border: 1px solid #4f4f4f; } + .checkbox-radio + label span:active { + border-color: #4f4f4f; } + .checkbox-radio + label.label-pos-left span { + margin-left: 4px; + margin-right: 0; } .checkbox-radio:checked[readonly] + label span { transition: none; } .checkbox-radio:checked[readonly] + label span::after { border-color: #4f4f4f !important; } - .checkbox-radio[readonly], - .checkbox-radio[readonly] + label { + .checkbox-radio[readonly], .checkbox-radio[readonly] + label { pointer-events: none; } - .checkbox-radio[readonly] span, - .checkbox-radio[readonly] + label span { + .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span { transition: none; } .checkbox-radio[type="checkbox"] + label span { border-radius: 2px; } @@ -3693,20 +3705,6 @@ select { border-radius: 50%; } .checkbox-radio[type="radio"]:checked[disabled] + label span::after { background-color: #4f4f4f !important; } - .checkbox-radio + label span { - position: relative; - display: inline-block; - width: 14px; - height: 14px; - margin: -4px 4px 0 0; - bottom: -3px; - border: 1px solid #4f4f4f; - background-color: #383838; } - .checkbox-radio + label span:active { - border-color: #4f4f4f; } - .checkbox-radio + label.label-pos-left span { - margin-left: 6px; - margin-right: 1px; } .checkbox-radio:focus + label span { border-color: #768d99; } .checkbox-radio:checked:focus + label span { @@ -3718,7 +3716,7 @@ select { .checkbox-radio[disabled] + label span { transition: none; } -input[type="checkbox"], input[type="radio"] { +input[type="radio"] { margin: 0; padding: 0; border: 0; @@ -4222,8 +4220,7 @@ button[disabled], button[disabled]:hover, button[disabled]:active { .radio-list-control input[type="radio"][disabled] + label, z-select[disabled] button, .z-select[disabled] button, z-select[readonly] button, -.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, -.checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { +.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { color: #525252; background-color: #2b2b2b !important; border-color: #3d3d3d; } @@ -5872,10 +5869,7 @@ svg { .checkbox-list li { overflow: hidden; text-overflow: ellipsis; - line-height: 20px; } - .checkbox-list li label { - line-height: 16px; - vertical-align: bottom; } + line-height: 24px; } .columns-wrapper { display: flex; @@ -5937,7 +5931,8 @@ svg { .preprocessing-list { display: block; max-width: 930px; - min-width: 800px; } + min-width: 800px; + position: relative; } .preprocessing-list > li { display: block; position: relative; } @@ -6411,47 +6406,44 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control { position: relative; - display: inline-block; - vertical-align: middle; } + display: inline-flex; } .range-control input[type=range] { + width: calc(100% + 10px); + height: 20px; + margin: 2px -5px; + padding: 0; + vertical-align: top; + opacity: 0; cursor: pointer; -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ -moz-appearance: none; /* Hides the slider so that custom slider can be made */ - width: calc(100% + 10px); - opacity: 0; - vertical-align: middle; - margin: 0 -5px; - padding: 0; - height: 20px; /* Special styling for WebKit/Blink */ } .range-control input[type=range]:focus { outline: none; } .range-control input[type=range]::-webkit-slider-thumb, .range-control input[type=range]::-moz-range-thumb { - margin-top: 1px; - /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ height: 10px; width: 10px; + margin-top: 1px; + /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ opacity: 0; } .range-control input[type=range]::-webkit-slider-runnable-track { height: 10px; opacity: 0; } .range-control input[type=text] { - width: 31px; - float: right; } + width: 31px; } .range-control > div { position: relative; width: calc(100% - 10px - 31px); - margin: 2px 10px 0 0; - float: left; } + margin-right: 10px; } .range-control .range-control-track, .range-control .range-control-progress { position: absolute; top: 50%; - margin-top: -1px; left: 0; height: 2px; + margin-top: -1px; cursor: pointer; } .range-control .range-control-track { width: 100%; @@ -6461,12 +6453,12 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control .range-control-thumb { position: absolute; top: 50%; - margin-top: -5px; - margin-left: -5px; width: 10px; height: 10px; - border-radius: 50%; - background: #4796c4; } + margin-top: -5px; + margin-left: -5px; + background: #4796c4; + border-radius: 50%; } .range-control.range-control-focus .range-control-thumb { margin-top: -6.66667px; margin-left: -6.66667px; @@ -6483,15 +6475,15 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu background: #c0c0c0; } .range-control datalist { position: absolute; + top: 50%; display: flex; width: 100%; - top: 50%; margin-top: -1px; } .range-control datalist option { - padding: 0; - font-size: 0; flex: 1 0 0; height: 2px; + padding: 0; + font-size: 0; border-left: 1px solid #0e1012; } .graph-legend { @@ -6991,9 +6983,9 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), background-position: -117px -506px !important; } .geomap-filter { - border: 2px solid rgba(48, 48, 48, 0.4); + border: 1px solid #383838; background-color: #2b2b2b; - padding: 10px 30px 5px 10px; + padding: 5px 20px 5px 10px; position: absolute; margin-right: -2px; margin-top: 5px; @@ -7001,7 +6993,7 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), right: 0; } .geomap-filter.collapsed { - display: table; } + display: block; } .geomap-filter li { width: calc(100% + 20px); } diff --git a/ui/assets/styles/hc-dark.css b/ui/assets/styles/hc-dark.css index 17e5c4287c2..4633a2344af 100644 --- a/ui/assets/styles/hc-dark.css +++ b/ui/assets/styles/hc-dark.css @@ -2611,8 +2611,9 @@ z-select, color: #ffffff; text-overflow: ellipsis; word-break: break-word; - white-space: pre-wrap; - user-select: none; } + user-select: none; + white-space: nowrap; + overflow: hidden; } z-select .list li.hover, .z-select .list li.hover { background-color: #454545; } @@ -2862,7 +2863,6 @@ form.is-loading { box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.1); color: #ffffff; height: 22px; - line-height: 1; vertical-align: middle; white-space: nowrap; } .list-table.compact-view td [class*='icon-'] { @@ -3605,19 +3605,31 @@ select { .checkbox-radio { position: absolute !important; top: auto !important; + z-index: 1; width: 16px !important; height: 16px !important; - opacity: 0; - z-index: 1; } + opacity: 0; } + .checkbox-radio + label span { + position: relative; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 4px; + vertical-align: text-bottom; + background-color: #383838; + border: 1px solid #666666; } + .checkbox-radio + label span:active { + border-color: #666666; } + .checkbox-radio + label.label-pos-left span { + margin-left: 4px; + margin-right: 0; } .checkbox-radio:checked[readonly] + label span { transition: none; } .checkbox-radio:checked[readonly] + label span::after { border-color: #666666 !important; } - .checkbox-radio[readonly], - .checkbox-radio[readonly] + label { + .checkbox-radio[readonly], .checkbox-radio[readonly] + label { pointer-events: none; } - .checkbox-radio[readonly] span, - .checkbox-radio[readonly] + label span { + .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span { transition: none; } .checkbox-radio[type="checkbox"] + label span { border-radius: 2px; } @@ -3648,20 +3660,6 @@ select { border-radius: 50%; } .checkbox-radio[type="radio"]:checked[disabled] + label span::after { background-color: #666666 !important; } - .checkbox-radio + label span { - position: relative; - display: inline-block; - width: 14px; - height: 14px; - margin: -4px 4px 0 0; - bottom: -3px; - border: 1px solid #666666; - background-color: #383838; } - .checkbox-radio + label span:active { - border-color: #666666; } - .checkbox-radio + label.label-pos-left span { - margin-left: 6px; - margin-right: 1px; } .checkbox-radio:focus + label span { border-color: #bbbbbb; } .checkbox-radio:checked:focus + label span { @@ -3673,7 +3671,7 @@ select { .checkbox-radio[disabled] + label span { transition: none; } -input[type="checkbox"], input[type="radio"] { +input[type="radio"] { margin: 0; padding: 0; border: 0; @@ -4177,8 +4175,7 @@ button[disabled], button[disabled]:hover, button[disabled]:active { .radio-list-control input[type="radio"][disabled] + label, z-select[disabled] button, .z-select[disabled] button, z-select[readonly] button, -.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, -.checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { +.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { color: #7d7d7d; background-color: #000000 !important; border-color: #5a5a5a; } @@ -5818,10 +5815,7 @@ svg { .checkbox-list li { overflow: hidden; text-overflow: ellipsis; - line-height: 20px; } - .checkbox-list li label { - line-height: 16px; - vertical-align: bottom; } + line-height: 24px; } .columns-wrapper { display: flex; @@ -5883,7 +5877,8 @@ svg { .preprocessing-list { display: block; max-width: 930px; - min-width: 800px; } + min-width: 800px; + position: relative; } .preprocessing-list > li { display: block; position: relative; } @@ -6357,47 +6352,44 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control { position: relative; - display: inline-block; - vertical-align: middle; } + display: inline-flex; } .range-control input[type=range] { + width: calc(100% + 10px); + height: 20px; + margin: 2px -5px; + padding: 0; + vertical-align: top; + opacity: 0; cursor: pointer; -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ -moz-appearance: none; /* Hides the slider so that custom slider can be made */ - width: calc(100% + 10px); - opacity: 0; - vertical-align: middle; - margin: 0 -5px; - padding: 0; - height: 20px; /* Special styling for WebKit/Blink */ } .range-control input[type=range]:focus { outline: none; } .range-control input[type=range]::-webkit-slider-thumb, .range-control input[type=range]::-moz-range-thumb { - margin-top: 1px; - /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ height: 10px; width: 10px; + margin-top: 1px; + /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ opacity: 0; } .range-control input[type=range]::-webkit-slider-runnable-track { height: 10px; opacity: 0; } .range-control input[type=text] { - width: 31px; - float: right; } + width: 31px; } .range-control > div { position: relative; width: calc(100% - 10px - 31px); - margin: 2px 10px 0 0; - float: left; } + margin-right: 10px; } .range-control .range-control-track, .range-control .range-control-progress { position: absolute; top: 50%; - margin-top: -1px; left: 0; height: 2px; + margin-top: -1px; cursor: pointer; } .range-control .range-control-track { width: 100%; @@ -6407,12 +6399,12 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control .range-control-thumb { position: absolute; top: 50%; - margin-top: -5px; - margin-left: -5px; width: 10px; height: 10px; - border-radius: 50%; - background: #f8f8f8; } + margin-top: -5px; + margin-left: -5px; + background: #f8f8f8; + border-radius: 50%; } .range-control.range-control-focus .range-control-thumb { margin-top: -6.66667px; margin-left: -6.66667px; @@ -6429,15 +6421,15 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu background: #c0c0c0; } .range-control datalist { position: absolute; + top: 50%; display: flex; width: 100%; - top: 50%; margin-top: -1px; } .range-control datalist option { - padding: 0; - font-size: 0; flex: 1 0 0; height: 2px; + padding: 0; + font-size: 0; border-left: 1px solid #111111; } .graph-legend { @@ -6937,9 +6929,9 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), background-position: -117px -506px !important; } .geomap-filter { - border: 2px solid rgba(68, 68, 68, 0.4); + border: 1px solid #666666; background-color: #000000; - padding: 10px 30px 5px 10px; + padding: 5px 20px 5px 10px; position: absolute; margin-right: -2px; margin-top: 5px; @@ -6947,7 +6939,7 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), right: 0; } .geomap-filter.collapsed { - display: table; } + display: block; } .geomap-filter li { width: calc(100% + 20px); } @@ -7364,6 +7356,11 @@ z-select button.focusable:focus, .list-table.compact-view td .icon-wizard-action { background: transparent url("../img/icon-sprite.svg?20211222") no-repeat -318px -624px; } +.overlay-dialogue .list-table th:first-child { + padding-left: 0px; } +.overlay-dialogue .list-table td:first-child { + padding-left: 42px; } + .filter-highlight-row-cb { display: none; } diff --git a/ui/assets/styles/hc-light.css b/ui/assets/styles/hc-light.css index efc7f4d8206..502fd43be4d 100644 --- a/ui/assets/styles/hc-light.css +++ b/ui/assets/styles/hc-light.css @@ -2611,8 +2611,9 @@ z-select, color: #000000; text-overflow: ellipsis; word-break: break-word; - white-space: pre-wrap; - user-select: none; } + user-select: none; + white-space: nowrap; + overflow: hidden; } z-select .list li.hover, .z-select .list li.hover { background-color: #f4f4f4; } @@ -2862,7 +2863,6 @@ form.is-loading { box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.1); color: #000000; height: 22px; - line-height: 1; vertical-align: middle; white-space: nowrap; } .list-table.compact-view td [class*='icon-'] { @@ -3605,19 +3605,31 @@ select { .checkbox-radio { position: absolute !important; top: auto !important; + z-index: 1; width: 16px !important; height: 16px !important; - opacity: 0; - z-index: 1; } + opacity: 0; } + .checkbox-radio + label span { + position: relative; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 4px; + vertical-align: text-bottom; + background-color: #ffffff; + border: 1px solid #888888; } + .checkbox-radio + label span:active { + border-color: #888888; } + .checkbox-radio + label.label-pos-left span { + margin-left: 4px; + margin-right: 0; } .checkbox-radio:checked[readonly] + label span { transition: none; } .checkbox-radio:checked[readonly] + label span::after { border-color: #888888 !important; } - .checkbox-radio[readonly], - .checkbox-radio[readonly] + label { + .checkbox-radio[readonly], .checkbox-radio[readonly] + label { pointer-events: none; } - .checkbox-radio[readonly] span, - .checkbox-radio[readonly] + label span { + .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span { transition: none; } .checkbox-radio[type="checkbox"] + label span { border-radius: 2px; } @@ -3648,20 +3660,6 @@ select { border-radius: 50%; } .checkbox-radio[type="radio"]:checked[disabled] + label span::after { background-color: #888888 !important; } - .checkbox-radio + label span { - position: relative; - display: inline-block; - width: 14px; - height: 14px; - margin: -4px 4px 0 0; - bottom: -3px; - border: 1px solid #888888; - background-color: #ffffff; } - .checkbox-radio + label span:active { - border-color: #888888; } - .checkbox-radio + label.label-pos-left span { - margin-left: 6px; - margin-right: 1px; } .checkbox-radio:focus + label span { border-color: #000000; } .checkbox-radio:checked:focus + label span { @@ -3673,7 +3671,7 @@ select { .checkbox-radio[disabled] + label span { transition: none; } -input[type="checkbox"], input[type="radio"] { +input[type="radio"] { margin: 0; padding: 0; border: 0; @@ -4177,8 +4175,7 @@ button[disabled], button[disabled]:hover, button[disabled]:active { .radio-list-control input[type="radio"][disabled] + label, z-select[disabled] button, .z-select[disabled] button, z-select[readonly] button, -.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, -.checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { +.z-select[readonly] button, .multiselect[aria-disabled], input[disabled], input[readonly], textarea[disabled], textarea[readonly], select option[disabled], select[disabled], select[readonly], .checkbox-radio[readonly] span, .checkbox-radio[readonly] + label span, .checkbox-radio[disabled] + label span, .macro-input-group button.btn-dropdown-toggle:disabled { color: #9f9f9f; background-color: #f0f0f0 !important; border-color: #9f9f9f; } @@ -5818,10 +5815,7 @@ svg { .checkbox-list li { overflow: hidden; text-overflow: ellipsis; - line-height: 20px; } - .checkbox-list li label { - line-height: 16px; - vertical-align: bottom; } + line-height: 24px; } .columns-wrapper { display: flex; @@ -5883,7 +5877,8 @@ svg { .preprocessing-list { display: block; max-width: 930px; - min-width: 800px; } + min-width: 800px; + position: relative; } .preprocessing-list > li { display: block; position: relative; } @@ -6357,47 +6352,44 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control { position: relative; - display: inline-block; - vertical-align: middle; } + display: inline-flex; } .range-control input[type=range] { + width: calc(100% + 10px); + height: 20px; + margin: 2px -5px; + padding: 0; + vertical-align: top; + opacity: 0; cursor: pointer; -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ -moz-appearance: none; /* Hides the slider so that custom slider can be made */ - width: calc(100% + 10px); - opacity: 0; - vertical-align: middle; - margin: 0 -5px; - padding: 0; - height: 20px; /* Special styling for WebKit/Blink */ } .range-control input[type=range]:focus { outline: none; } .range-control input[type=range]::-webkit-slider-thumb, .range-control input[type=range]::-moz-range-thumb { - margin-top: 1px; - /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ height: 10px; width: 10px; + margin-top: 1px; + /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */ opacity: 0; } .range-control input[type=range]::-webkit-slider-runnable-track { height: 10px; opacity: 0; } .range-control input[type=text] { - width: 31px; - float: right; } + width: 31px; } .range-control > div { position: relative; width: calc(100% - 10px - 31px); - margin: 2px 10px 0 0; - float: left; } + margin-right: 10px; } .range-control .range-control-track, .range-control .range-control-progress { position: absolute; top: 50%; - margin-top: -1px; left: 0; height: 2px; + margin-top: -1px; cursor: pointer; } .range-control .range-control-track { width: 100%; @@ -6407,12 +6399,12 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu .range-control .range-control-thumb { position: absolute; top: 50%; - margin-top: -5px; - margin-left: -5px; width: 10px; height: 10px; - border-radius: 50%; - background: #555555; } + margin-top: -5px; + margin-left: -5px; + background: #555555; + border-radius: 50%; } .range-control.range-control-focus .range-control-thumb { margin-top: -6.66667px; margin-left: -6.66667px; @@ -6429,15 +6421,15 @@ button.icon-action-command, button.icon-action-close, button.icon-action-msg, bu background: #c0c0c0; } .range-control datalist { position: absolute; + top: 50%; display: flex; width: 100%; - top: 50%; margin-top: -1px; } .range-control datalist option { - padding: 0; - font-size: 0; flex: 1 0 0; height: 2px; + padding: 0; + font-size: 0; border-left: 1px solid #ffffff; } .graph-legend { @@ -6937,9 +6929,9 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), background-position: -117px -506px !important; } .geomap-filter { - border: 2px solid rgba(159, 159, 159, 0.4); + border: 1px solid #383838; background-color: #ffffff; - padding: 10px 30px 5px 10px; + padding: 5px 20px 5px 10px; position: absolute; margin-right: -2px; margin-top: 5px; @@ -6947,7 +6939,7 @@ z-select.z-select-host-interface li[disabled] .description:not(:empty), right: 0; } .geomap-filter.collapsed { - display: table; } + display: block; } .geomap-filter li { width: calc(100% + 20px); } @@ -7300,6 +7292,11 @@ z-select button.focusable:focus, .list-table.compact-view td .icon-wizard-action { background: transparent url("../img/icon-sprite.svg?20211222") no-repeat -165px -624px; } +.overlay-dialogue .list-table th:first-child { + padding-left: 0px; } +.overlay-dialogue .list-table td:first-child { + padding-left: 42px; } + .filter-highlight-row-cb { display: none; } diff --git a/ui/graphs.php b/ui/graphs.php index 9bdc21f9267..79ba750cd8c 100644 --- a/ui/graphs.php +++ b/ui/graphs.php @@ -62,6 +62,7 @@ $fields = [ 'group_graphid' => [T_ZBX_INT, O_OPT, null, DB_ID, null], 'copy_targetids' => [T_ZBX_INT, O_OPT, null, DB_ID, null], 'context' => [T_ZBX_STR, O_MAND, P_SYS, IN('"host", "template"'), null], + 'readonly' => [T_ZBX_INT, O_OPT, null, IN('1'), null], // actions 'action' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, IN('"graph.masscopyto","graph.massdelete","graph.updatediscover"'), null], 'add' => [T_ZBX_STR, O_OPT, P_SYS|P_ACT, null, null], @@ -502,10 +503,11 @@ elseif (isset($_REQUEST['form'])) { 'group_gid' => getRequest('group_gid', []), 'hostid' => $hostid, 'normal_only' => getRequest('normal_only'), - 'context' => getRequest('context') + 'context' => getRequest('context'), + 'readonly' => getRequest('readonly', 0) ]; - if (!empty($data['graphid']) && !isset($_REQUEST['form_refresh'])) { + if ($data['graphid'] != 0 && ($data['readonly'] || !$data['form_refresh'])) { $options = [ 'graphids' => $data['graphid'], 'output' => API_OUTPUT_EXTEND, @@ -612,7 +614,7 @@ elseif (isset($_REQUEST['form'])) { } } - if (empty($data['graphid']) && !isset($_REQUEST['form_refresh'])) { + if ($data['graphid'] == 0 && !$data['form_refresh']) { $data['show_legend'] = $_REQUEST['show_legend'] = 1; $data['show_work_period'] = $_REQUEST['show_work_period'] = 1; $data['show_triggers'] = $_REQUEST['show_triggers'] = 1; diff --git a/ui/include/classes/graphdraw/CLineGraphDraw.php b/ui/include/classes/graphdraw/CLineGraphDraw.php index a4988b1e782..585fe26262f 100644 --- a/ui/include/classes/graphdraw/CLineGraphDraw.php +++ b/ui/include/classes/graphdraw/CLineGraphDraw.php @@ -930,12 +930,14 @@ class CLineGraphDraw extends CGraphDraw { /** * Get best matching X-axis interval specification for the preferred sub-interval. * - * @param int $pref_sub_interval Preferred sub-interval in seconds. - * @param float $min_sub_interval Preferred minimal sub-interval in seconds (float). Discarded if no matches. + * @param int $pref_sub_interval Preferred sub-interval in seconds. + * @param float $min_sub_interval Preferred minimal sub-interval in seconds (float). Discarded if no matches. + * @param string $magnitude The highest non-permanent date component (Y, m, d, H, i, s). * * @return array */ - private function getOptimalDateTimeIntervalSpec(int $pref_sub_interval, float $min_sub_interval): array { + private function getOptimalDateTimeIntervalSpec(int $pref_sub_interval, float $min_sub_interval, + string $magnitude): array { // Possible X-axis main and sub-intervals. $intervals = [ 'PT1M' => ['PT1S', 'PT5S', 'PT10S', 'PT30S'], @@ -962,9 +964,9 @@ class CLineGraphDraw extends CGraphDraw { $formats = [ 'PT1M' => ['main' => TIME_FORMAT, 'sub' => _('H:i:s')], 'PT1H' => ['main' => TIME_FORMAT, 'sub' => TIME_FORMAT], - 'P1D' => ['main' => _('m-d'), 'sub' => TIME_FORMAT], - 'P1W' => ['main' => _('m-d'), 'sub' => _('m-d')], - 'P1M' => ['main' => _('m-d'), 'sub' => _('m-d')], + 'P1D' => ['main' => $magnitude === 'Y' ? DATE_FORMAT : _('m-d'), 'sub' => TIME_FORMAT], + 'P1W' => ['main' => $magnitude === 'Y' ? DATE_FORMAT : _('m-d'), 'sub' => _('m-d')], + 'P1M' => ['main' => $magnitude === 'Y' ? DATE_FORMAT : _('m-d'), 'sub' => _('m-d')], 'P1Y' => ['main' => _x('Y', DATE_FORMAT_CONTEXT), 'sub' => _('M')], 'P10Y' => ['main' => _x('Y', DATE_FORMAT_CONTEXT), 'sub' => _x('Y', DATE_FORMAT_CONTEXT)] ]; @@ -1082,7 +1084,13 @@ class CLineGraphDraw extends CGraphDraw { $preferred_sub_interval = (int) ($this->period * $this->cell_width / $this->sizeX); - $optimal = $this->getOptimalDateTimeIntervalSpec($preferred_sub_interval, $label_size); + foreach (['Y', 'm', 'd', 'H', 'i', 's'] as $magnitude) { + if (date($magnitude, $this->stime) !== date($magnitude, $this->stime + $this->period)) { + break; + } + } + + $optimal = $this->getOptimalDateTimeIntervalSpec($preferred_sub_interval, $label_size, $magnitude); // Align starting date and time with the interval. $start = strtotime(date($optimal['aligner']['trim'], $this->stime)); diff --git a/ui/include/classes/html/CTabView.php b/ui/include/classes/html/CTabView.php index a01c243d1a0..c913c4d3222 100644 --- a/ui/include/classes/html/CTabView.php +++ b/ui/include/classes/html/CTabView.php @@ -154,17 +154,18 @@ class CTabView extends CDiv { } public function makeJavascript() { + $tab_name = ':tab.'.$this->id; $create_event = ''; if ($this->selectedTab !== null) { $create_event = 'create: function() {'. - 'sessionStorage.setItem(ZBX_SESSION_NAME + "_tab", '.json_encode($this->selectedTab).');'. + 'sessionStorage.setItem(ZBX_SESSION_NAME + "'.$tab_name.'", '.json_encode($this->selectedTab).');'. '},'; $active_tab = 'active: '.json_encode($this->selectedTab).','; } else { $active_tab = 'active: function() {'. - 'return sessionStorage.getItem(ZBX_SESSION_NAME + "_tab") || 0;'. + 'return sessionStorage.getItem(ZBX_SESSION_NAME + "'.$tab_name.'") || 0;'. '}(),'; } @@ -177,14 +178,14 @@ class CTabView extends CDiv { $disabled_tabs. $active_tab. 'activate: function(event, ui) {'. - 'sessionStorage.setItem(ZBX_SESSION_NAME + "_tab", ui.newTab.index().toString());'. + 'sessionStorage.setItem(ZBX_SESSION_NAME + "'.$tab_name.'", ui.newTab.index().toString());'. 'jQuery.cookie("tab", ui.newTab.index().toString());'. $this->tab_change_js. '}'. '})'. // Prevent changing the cookie value in a different tab. '.parent().on("submit", function() {'. - 'jQuery.cookie("tab", sessionStorage.getItem(ZBX_SESSION_NAME + "_tab") || 0);'. + 'jQuery.cookie("tab", sessionStorage.getItem(ZBX_SESSION_NAME + "'.$tab_name.'") || 0);'. '});'; } } diff --git a/ui/include/classes/import/CConfigurationImport.php b/ui/include/classes/import/CConfigurationImport.php index 86a94f43935..653b0230bb0 100644 --- a/ui/include/classes/import/CConfigurationImport.php +++ b/ui/include/classes/import/CConfigurationImport.php @@ -2346,15 +2346,11 @@ class CConfigurationImport { return; } - $dashboards = $this->getFormattedTemplateDashboards(); - - if ($dashboards) { - $dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer, - $this->importedObjectContainer - ); + $dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer, + $this->importedObjectContainer + ); - $dashboard_importer->delete($dashboards); - } + $dashboard_importer->delete($this->getFormattedTemplateDashboards()); } /** @@ -2561,7 +2557,7 @@ class CConfigurationImport { */ protected function getFormattedTemplateDashboards(): array { if (!array_key_exists('templateDashboards', $this->formattedData)) { - $this->formattedData['templateDashboards'] = $this->adapter->getTemplateDashboards(); + $this->formattedData['templateDashboards'] = $this->adapter->getTemplateDashboards(); } return $this->formattedData['templateDashboards']; diff --git a/ui/include/views/configuration.graph.edit.php b/ui/include/views/configuration.graph.edit.php index a241086e79a..b772e286212 100644 --- a/ui/include/views/configuration.graph.edit.php +++ b/ui/include/views/configuration.graph.edit.php @@ -74,6 +74,7 @@ if (array_key_exists('flags', $data) && $data['flags'] == ZBX_FLAG_DISCOVERY_CRE $readonly = false; if ($is_templated || $discovered_graph) { $readonly = true; + $graphForm->addItem((new CVar('readonly', 1))->removeId()); } if ($discovered_graph) { @@ -431,9 +432,12 @@ if ($data['parent_discoveryid']) { // Append tabs to form. $graphTab = (new CTabView()) - ->setSelected(0) ->addTab('graphTab', ($data['parent_discoveryid'] === null) ? _('Graph') : _('Graph prototype'), $graphFormList); +if (!$data['form_refresh']) { + $graphTab->setSelected(0); +} + /* * Preview tab */ diff --git a/ui/include/views/js/configuration.graph.edit.js.php b/ui/include/views/js/configuration.graph.edit.js.php index 2c883492d2b..8a1a32787ce 100644 --- a/ui/include/views/js/configuration.graph.edit.js.php +++ b/ui/include/views/js/configuration.graph.edit.js.php @@ -337,8 +337,10 @@ this.loadItem(item); }); - $('#tabs').on('tabsactivate', (event, ui) => { - if (ui.newPanel.attr('id') === 'previewTab') { + $('#tabs').on('tabscreate tabsactivate', (event, ui) => { + const $panel = (event.type === 'tabscreate') ? ui.panel : ui.newPanel; + + if ($panel.attr('id') === 'previewTab') { const $preview_chart = $('#previewChart'); const src = new Curl('chart3.php'); diff --git a/ui/js/class.tab-indicators.js b/ui/js/class.tab-indicators.js index f8cf63a1d59..6ed5bdb8557 100644 --- a/ui/js/class.tab-indicators.js +++ b/ui/js/class.tab-indicators.js @@ -537,7 +537,7 @@ class PreprocessingTabIndicatorItem extends TabIndicatorItem { getValue() { return document - .querySelectorAll('#preprocessing .preprocessing-list-item') + .querySelectorAll('#preprocessing .preprocessing-list-item:not(.ui-sortable-placeholder)') .length; } diff --git a/ui/js/widgets/class.widget.js b/ui/js/widgets/class.widget.js index 5640bdc58e4..afddb550969 100644 --- a/ui/js/widgets/class.widget.js +++ b/ui/js/widgets/class.widget.js @@ -733,7 +733,9 @@ class CWidget extends CBaseComponent { this._content_body.insertAdjacentHTML('beforeend', messages); } - this._content_body.insertAdjacentHTML('beforeend', body); + if (body !== undefined) { + this._content_body.insertAdjacentHTML('beforeend', body); + } if (debug !== undefined) { this._content_body.insertAdjacentHTML('beforeend', debug); diff --git a/ui/js/widgets/class.widget.navtree.js b/ui/js/widgets/class.widget.navtree.js index e25c364d996..c424c86c8f8 100644 --- a/ui/js/widgets/class.widget.navtree.js +++ b/ui/js/widgets/class.widget.navtree.js @@ -961,7 +961,8 @@ class CWidgetNavTree extends CWidget { 'action': () => {} } ], - 'dialogueid': 'navtreeitem' + 'dialogueid': 'navtreeitem', + 'script_inline': resp.script_inline }, trigger_elmnt); } }); diff --git a/ui/tests/integration/IntegrationTests.php b/ui/tests/integration/IntegrationTests.php index 88406df41a7..e833788652b 100644 --- a/ui/tests/integration/IntegrationTests.php +++ b/ui/tests/integration/IntegrationTests.php @@ -27,6 +27,7 @@ require_once dirname(__FILE__).'/testValuemaps.php'; require_once dirname(__FILE__).'/testTriggerLinking.php'; require_once dirname(__FILE__).'/testGraphLinking.php'; require_once dirname(__FILE__).'/testEscalations.php'; +require_once dirname(__FILE__).'/testAlertingForServices.php'; require_once dirname(__FILE__).'/testComplexServiceStatus.php'; require_once dirname(__FILE__).'/testServiceRoles.php'; require_once dirname(__FILE__).'/testExpressionMacros.php'; @@ -52,6 +53,7 @@ class IntegrationTests { $suite->addTestSuite('testTriggerLinking'); $suite->addTestSuite('testGraphLinking'); $suite->addTestSuite('testEscalations'); + $suite->addTestSuite('testAlertingForServices'); $suite->addTestSuite('testComplexServiceStatus'); $suite->addTestSuite('testServiceRoles'); $suite->addTestSuite('testExpressionMacros'); diff --git a/ui/tests/integration/testAlertingForServices.php b/ui/tests/integration/testAlertingForServices.php new file mode 100644 index 00000000000..5d3fb5bb60a --- /dev/null +++ b/ui/tests/integration/testAlertingForServices.php @@ -0,0 +1,614 @@ +<?php +/* +** Zabbix +** Copyright (C) 2001-2021 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. +**/ + +require_once dirname(__FILE__).'/../include/CIntegrationTest.php'; + +/** + * Test suite for alerting for services. + * + * @required-components server + * @configurationDataProvider serverConfigurationProvider + * @hosts test_services_alerting + * @backup history,items,triggers,alerts,services + */ +class testAlertingForServices extends CIntegrationTest { + const HOSTNAME = 'test_services_alerting'; + const TRAPPER_KEY = 'test_trapper'; + const SERVICENAME = 'Service 1'; + const TRIGGER_DESC = 'Sample trigger'; + + private static $hostid; + private static $serviceid; + private static $actionid; + private static $triggerid; + private static $problem_tags; + + private function createServiceWithProblemTags($name, $problem_tags) { + $response = $this->call('service.create', [ + 'name' => $name, + 'algorithm' => ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ALL, + 'sortorder' => 1, + 'problem_tags' => $problem_tags + ]); + $this->assertArrayHasKey('serviceids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['serviceids']); + + return $response['result']['serviceids'][0]; + } + + /** + * @inheritdoc + */ + public function prepareData() { + // Create host "test_services_alerting" + $response = $this->call('host.create', [ + [ + 'host' => self::HOSTNAME, + 'interfaces' => [ + 'type' => 1, + 'main' => 1, + 'useip' => 1, + 'ip' => '127.0.0.1', + 'dns' => '', + 'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort') + ], + 'groups' => [['groupid' => 4]], + 'status' => HOST_STATUS_MONITORED + ] + ]); + + $this->assertArrayHasKey('hostids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['hostids']); + self::$hostid = $response['result']['hostids'][0]; + + // Create trapper item + $response = $this->call('item.create', [ + [ + 'name' => self::TRAPPER_KEY, + 'key_' => self::TRAPPER_KEY, + 'type' => ITEM_TYPE_TRAPPER, + 'hostid' => self::$hostid, + 'value_type' => ITEM_VALUE_TYPE_UINT64 + ] + ]); + $this->assertArrayHasKey('itemids', $response['result']); + $this->assertEquals(1, count($response['result']['itemids'])); + + // Create trigger + $response = $this->call('trigger.create', [ + 'description' => self::TRIGGER_DESC, + 'priority' => TRIGGER_SEVERITY_HIGH, + 'status' => TRIGGER_STATUS_ENABLED, + 'type' => 0, + 'recovery_mode' => 0, + 'manual_close' => ZBX_TRIGGER_MANUAL_CLOSE_NOT_ALLOWED, + 'expression' => 'last(/' . self::HOSTNAME . '/' . self::TRAPPER_KEY . ')=1' + ]); + $this->assertArrayHasKey('triggerids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['triggerids']); + self::$triggerid = $response['result']['triggerids'][0]; + + $response = $this->call('trigger.update', [ + 'triggerid' => self::$triggerid, + 'tags' => [ + [ + 'tag' => 'ServiceLink', + 'value' => self::$triggerid . ':' . self::TRIGGER_DESC + ] + ] + ]); + self::$problem_tags = 'ServiceLink:' . self::$triggerid . ':' . self::TRIGGER_DESC; + + // Create service + self::$serviceid = $this->createServiceWithProblemTags(self::SERVICENAME, [ + [ + 'tag' => 'ServiceLink', + 'operator' => 0, + 'value' => self::$triggerid . ':' . self::TRIGGER_DESC + ] + ]); + + // Create action + $response = $this->call('action.create', [ + 'name' => 'Sample service status changed', + 'eventsource' => EVENT_SOURCE_SERVICE, + 'status' => 0, + 'esc_period' => '1h', + 'filter' => [ + 'evaltype' => 0, + 'conditions' => [ + [ + 'conditiontype' => CONDITION_TYPE_SERVICE, + 'operator' => 0, + 'value' => self::$serviceid + ] + ] + ], + 'operations' => [ + [ + 'esc_period' => 0, + 'esc_step_from' => 1, + 'esc_step_to' => 1, + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Problem', + 'subject' => 'Problem' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ], + 'recovery_operations' => [ + [ + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Recovery', + 'subject' => 'Recovery' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ], + 'update_operations' => [ + [ + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Update', + 'subject' => 'Update' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ] + ] + ); + $this->assertArrayHasKey('actionids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['actionids']); + self::$actionid = $response['result']['actionids'][0]; + + return true; + } + + /** + * Component configuration provider for agent related tests. + * + * @return array + */ + public function serverConfigurationProvider() { + return [ + self::COMPONENT_SERVER => [ + 'DebugLevel' => 4, + 'LogFileSize' => 20 + ] + ]; + } + + /** + * Check alerting based on service action. + * + * @backup actions,alerts,history_uint,events,problem,service_problem,triggers,escalations + */ + public function testAlertingForServices_checkServiceStatusChange() { + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 1); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE + ], 25, 2); + $this->assertCount(1, $response['result']); + $this->assertEquals('Problem', $response['result'][0]['message']); + $this->assertEquals('Problem', $response['result'][0]['subject']); + $this->assertEquals(0, $response['result'][0]['p_eventid']); + + $response = $this->callUntilDataIsPresent('event.get', [ + 'objectids' => self::$triggerid + ], 25, 2); + $this->assertArrayHasKey(0, $response['result']); + $eventid = $response['result'][0]['eventid']; + + $response = $this->call('event.acknowledge', [ + 'eventids' => $eventid, + 'action' => ZBX_PROBLEM_UPDATE_SEVERITY, + 'message' => 'disaster', + 'severity' => TRIGGER_SEVERITY_DISASTER + ]); + $this->assertArrayHasKey('eventids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['eventids']); + + sleep(12); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE + ], 25, 2); + $this->assertCount(2, $response['result']); + + // Check if new problem event was added + $response = $this->call('problem.get', [ + 'output' => 'extend', + 'source' => EVENT_SOURCE_SERVICE, + 'object' => EVENT_OBJECT_SERVICE + ] + ); + $this->assertArrayHasKey(0, $response['result']); + $this->assertEquals(TRIGGER_SEVERITY_HIGH, $response['result'][0]['severity']); + $expected_eventname = 'Status of service "' . self::SERVICENAME . '" changed to High'; + $this->assertEquals($expected_eventname, $response['result'][0]['name']); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 0); + + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'In escalation_recover()', true, 120); + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of escalation_recover()', true); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE, + 'sortfield' => 'alertid' + ], 25, 3); + $this->assertCount(3, $response['result']); + $this->assertEquals('Recovery', $response['result'][2]['message']); + $this->assertEquals('Recovery', $response['result'][2]['subject']); + $this->assertNotEquals(0, $response['result'][2]['p_eventid']); + + // Check recovery event + $response = $this->callUntilDataIsPresent('event.get', [ + 'output' => 'extend', + 'source' => EVENT_SOURCE_SERVICE, + 'object' => EVENT_OBJECT_SERVICE, + 'value' => 0 + ], 25, 3); + $this->assertArrayHasKey(0, $response['result']); + $this->assertEquals(ZBX_SEVERITY_OK, $response['result'][0]['severity']); + $expected_eventname = 'Status of service "' . self::SERVICENAME . '" changed to OK'; + $this->assertEquals($expected_eventname, $response['result'][0]['name']); + + return true; + } + + /** + * Check alerting based on service action (without update operation, but with updated severity). + * + * @backup actions,alerts,history_uint,events,problem,service_problem,triggers,escalations + */ + public function testAlertingForServices_checkServiceStatusChangeWoUpdate() { + $response = $this->call('action.update', [ + 'actionid' => self::$actionid, + 'esc_period' => '1m', + 'operations' => [ + [ + 'esc_period' => 0, + 'esc_step_from' => 1, + 'esc_step_to' => 1, + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Problem', + 'subject' => 'Problem' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ], + 'recovery_operations' => [ + [ + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Recovery', + 'subject' => 'Recovery' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ], + 'update_operations' => [ + ] + ]); + $this->assertArrayHasKey('actionids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['actionids']); + + $this->reloadConfigurationCache(); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 1); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE + ], 25, 2); + $this->assertCount(1, $response['result']); + $this->assertEquals('Problem', $response['result'][0]['message']); + $this->assertEquals('Problem', $response['result'][0]['subject']); + $this->assertEquals(0, $response['result'][0]['p_eventid']); + + $response = $this->callUntilDataIsPresent('event.get', [ + 'objectids' => self::$triggerid + ], 25, 2); + $this->assertArrayHasKey(0, $response['result']); + $eventid = $response['result'][0]['eventid']; + + $response = $this->call('event.acknowledge', [ + 'eventids' => $eventid, + 'action' => ZBX_PROBLEM_UPDATE_SEVERITY, + 'message' => 'disaster', + 'severity' => TRIGGER_SEVERITY_DISASTER + ]); + $this->assertArrayHasKey('eventids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['eventids']); + + sleep(10); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE + ], 25, 2); + $this->assertCount(1, $response['result']); + + // Check if new problem event was added + $response = $this->call('problem.get', [ + 'output' => 'extend', + 'source' => EVENT_SOURCE_SERVICE, + 'object' => EVENT_OBJECT_SERVICE + ] + ); + $this->assertArrayHasKey(0, $response['result']); + $this->assertEquals(TRIGGER_SEVERITY_HIGH, $response['result'][0]['severity']); + $expected_eventname = 'Status of service "' . self::SERVICENAME . '" changed to High'; + $this->assertEquals($expected_eventname, $response['result'][0]['name']); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 0); + + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'In escalation_recover()', true, 120); + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of escalation_recover()', true); + + $response = $this->callUntilDataIsPresent('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE, + 'sortfield' => 'alertid' + ], 25, 3); + $this->assertCount(2, $response['result']); + $this->assertEquals('Recovery', $response['result'][1]['message']); + $this->assertEquals('Recovery', $response['result'][1]['subject']); + $this->assertNotEquals(0, $response['result'][1]['p_eventid']); + + // Check recovery event + $response = $this->callUntilDataIsPresent('event.get', [ + 'output' => 'extend', + 'source' => EVENT_SOURCE_SERVICE, + 'object' => EVENT_OBJECT_SERVICE, + 'value' => 0 + ], 25, 3); + $this->assertArrayHasKey(0, $response['result']); + $this->assertEquals(ZBX_SEVERITY_OK, $response['result'][0]['severity']); + $expected_eventname = 'Status of service "' . self::SERVICENAME . '" changed to OK'; + $this->assertEquals($expected_eventname, $response['result'][0]['name']); + + return true; + } + + /** + * Check SERVICE.* macros. + * + * @backup alerts,history,events,triggers,services + */ + public function testAlertingForServices_checkMacros() { + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 0); + $this->reloadConfigurationCache(); + + $response = $this->call('action.update', [ + 'actionid' => self::$actionid, + 'esc_period' => '1m', + 'operations' => [ + [ + 'esc_period' => 0, + 'esc_step_from' => 1, + 'esc_step_to' => 1, + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => '{SERVICE.NAME}|{SERVICE.TAGS}|{SERVICE.TAGSJSON}|{SERVICE.ROOTCAUSE}', + 'subject' => 'Problem' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ] + ]); + $this->assertArrayHasKey('actionids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['actionids']); + + $this->reloadConfigurationCache(); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 1); + + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'In escalation_execute()', true, 120); + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of escalation_execute()', true); + + $response = $this->call('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE + ]); + $this->assertArrayHasKey(0, $response['result']); + + $service_macros = explode('|', $response['result'][0]['message']); + + $rootcause = '/Host: "' . self::HOSTNAME . '" Problem name: "' . self::TRIGGER_DESC .'" Severity: "' . + 'High" Age: [0-9]+s Problem tags: "' . self::$problem_tags . '"/ '; + + $this->assertEquals(self::SERVICENAME, $service_macros[0]); + $this->assertEmpty($service_macros[1]); + $this->assertEquals('[]', $service_macros[2]); + + $this->assertEquals(1, preg_match($rootcause, $service_macros[3], $matches)); + } + + /** + * Check escalation steps. + * + * @backup alerts,history,events,triggers,services + */ + public function testAlertingForServices_checkEscalationSteps() { + $response = $this->call('action.update', [ + 'actionid' => self::$actionid, + 'esc_period' => '1m', + 'operations' => [ + [ + 'esc_period' => 0, + 'esc_step_from' => 1, + 'esc_step_to' => 1, + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Problem', + 'subject' => 'Problem' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ], + [ + 'esc_period' => 0, + 'esc_step_from' => 2, + 'esc_step_to' => 2, + 'operationtype' => OPERATION_TYPE_MESSAGE, + 'opmessage' => [ + 'default_msg' => 0, + 'mediatypeid' => 0, + 'message' => 'Problem', + 'subject' => 'Problem' + ], + 'opmessage_grp' => [['usrgrpid' => 7]] + ] + ] + ]); + $this->assertArrayHasKey('actionids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['actionids']); + + $this->reloadConfigurationCache(); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 1); + + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'In escalation_execute()', true, 120); + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of escalation_execute()', true); + + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'In escalation_execute()', true, 120); + $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of escalation_execute()', true); + + $response = $this->call('alert.get', [ + 'output' => 'extend', + 'actionsids' => self::$actionid, + 'eventsource' => EVENT_SOURCE_SERVICE, + 'eventobject' => EVENT_OBJECT_SERVICE, + 'sortfield' => 'alertid' + ] + ); + $this->assertCount(2, $response['result']); + $this->assertEquals(1, $response['result'][0]['esc_step']); + $this->assertEquals(2, $response['result'][1]['esc_step']); + + return true; + } + + /** + * Test suite for alerting for services. + * + * @backup alerts,history,events,triggers,services + */ + public function testAlertingForServices_checkTwoTriggers() { + $this->reloadConfigurationCache(); + // Create trigger + $trigger_desc_2 = self::TRIGGER_DESC . '2'; + $response = $this->call('trigger.create', [ + 'description' => $trigger_desc_2, + 'priority' => TRIGGER_SEVERITY_DISASTER, + 'status' => TRIGGER_STATUS_ENABLED, + 'type' => 0, + 'recovery_mode' => 0, + 'manual_close' => ZBX_TRIGGER_MANUAL_CLOSE_NOT_ALLOWED, + 'expression' => 'last(/' . self::HOSTNAME . '/' . self::TRAPPER_KEY . ')=1' + ]); + $this->assertArrayHasKey('triggerids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['triggerids']); + $trigger_id_2 = $response['result']['triggerids'][0]; + + $response = $this->call('trigger.update', [ + 'triggerid' => $trigger_id_2, + 'tags' => [ + [ + 'tag' => 'ServiceLink', + 'value' => $trigger_id_2 . ':' . $trigger_desc_2 + ] + ] + ]); + + $serviceid_child1 = $this->createServiceWithProblemTags('Child service (disaster)', [[ + 'tag' => 'ServiceLink', + 'operator' => 0, + 'value' => $trigger_id_2 . ':' . $trigger_desc_2 + ]]); + + $serviceid_child2 = $this->createServiceWithProblemTags('Child service (high)', [[ + 'tag' => 'ServiceLink', + 'operator' => 0, + 'value' => self::$triggerid . ':' . self::TRIGGER_DESC + ]]); + + // Update service + $response = $this->call('service.update', [ + 'serviceid' => self::$serviceid, + 'children' => [ + ['serviceid' => $serviceid_child1], + ['serviceid' => $serviceid_child2] + ], + 'problem_tags' => [] + ]); + $this->assertArrayHasKey('serviceids', $response['result']); + $this->assertArrayHasKey(0, $response['result']['serviceids']); + + $this->reloadConfigurationCache(); + + $this->sendSenderValue(self::HOSTNAME, self::TRAPPER_KEY, 1); + + $response = $this->callUntilDataIsPresent('service.get', [ + 'output' => 'extend', + 'serviceids' => self::$serviceid + ], 25, 3); + $this->assertArrayHasKey(0, $response['result']); + $this->assertArrayHasKey('status', $response['result'][0]); + $this->assertEquals(TRIGGER_SEVERITY_DISASTER, $response['result'][0]['status']); + + return true; + } +} diff --git a/ui/tests/selenium/common/testSystemInformation.php b/ui/tests/selenium/common/testSystemInformation.php index f4a966fc7f4..181874c04d4 100644 --- a/ui/tests/selenium/common/testSystemInformation.php +++ b/ui/tests/selenium/common/testSystemInformation.php @@ -77,7 +77,7 @@ class testSystemInformation extends CWebTest { 'ha_nodeid' => 'ckvaw9wlf0001tn7psxgh3wfo', 'name' => 'Active node', 'address' => $DB['SERVER'], - 'port' => $DB['PORT'], + 'port' => 0, 'lastaccess' => self::$active_lastaccess, 'status' => 3, 'ha_sessionid' => 'ckvaw9wjo0000td7p8j66e74x' @@ -174,7 +174,7 @@ class testSystemInformation extends CWebTest { // Check Zabbix server address and port for each record in the HA cluster nodes table. if ($name === 'Active node') { self::$skip_fields[] = $row->getColumn('Address'); - $this->assertEquals($DB['SERVER'].':'.$DB['PORT'], $row->getColumn('Address')->getText()); + $this->assertEquals($DB['SERVER'].':0', $row->getColumn('Address')->getText()); } } @@ -182,7 +182,7 @@ class testSystemInformation extends CWebTest { * Check and hide the active Zabbix server address in widget that is working in System stats mode or in the part * of the report that displays the overall system statistics. */ - $this->assertEquals($DB['SERVER'].':'.$DB['PORT'], $server_address->getText()); + $this->assertEquals($DB['SERVER'].':0', $server_address->getText()); self::$skip_fields[] = $server_address; // Hide the footer of the report as it contains Zabbix version. diff --git a/ui/tests/selenium/testFormAdministrationAuthenticationLdap.php b/ui/tests/selenium/testFormAdministrationAuthenticationLdap.php index d9934a19ebc..2ee9a6f8538 100644 --- a/ui/tests/selenium/testFormAdministrationAuthenticationLdap.php +++ b/ui/tests/selenium/testFormAdministrationAuthenticationLdap.php @@ -44,7 +44,7 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { [ 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com' + 'LDAP host' => 'ipa.demo1.freeipa.org' ], 'error' => 'Incorrect value for field "ldap_base_dn": cannot be empty.' ] @@ -53,8 +53,8 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { [ 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com', - 'Base DN' => 'dc=example,dc=com' + 'LDAP host' => 'ipa.demo1.freeipa.org', + 'Base DN' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org' ], 'error' => 'Incorrect value for field "ldap_search_attribute": cannot be empty.' ] @@ -63,8 +63,8 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { [ 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com', - 'Base DN' => 'dc=example,dc=com', + 'LDAP host' => 'ipa.demo1.freeipa.org', + 'Base DN' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Search attribute' => 'uid' ], 'error' => 'Login name or password is incorrect.' @@ -74,10 +74,10 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { [ 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com', - 'Base DN' => 'dc=example,dc=com', + 'LDAP host' => 'ipa.demo1.freeipa.org', + 'Base DN' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Search attribute' => 'uid', - 'Login' => 'galieleo' + 'Login' => 'admin' ], 'error' => 'Login name or password is incorrect.' ] @@ -86,10 +86,10 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { [ 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com', - 'Base DN' => 'dc=example,dc=com', + 'LDAP host' => 'ipa.demo1.freeipa.org', + 'Base DN' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Search attribute' => 'uid', - 'Bind password' => 'password' + 'Bind password' => 'Secret123' ], 'error' => 'Incorrect value for field "ldap_bind_dn": cannot be empty.' ] @@ -100,23 +100,23 @@ class testFormAdministrationAuthenticationLdap extends CWebTest { 'password' => 'zabbix', 'ldap_settings' => [ 'Enable LDAP authentication' => true, - 'LDAP host' => 'ldap.forumsys.com', + 'LDAP host' => 'ipa.demo1.freeipa.org', 'Port' => '389', - 'Base DN' => 'dc=example,dc=com', + 'Base DN' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Search attribute' => 'uid', - 'Bind DN' => 'cn=read-only-admin,dc=example,dc=com', + 'Bind DN' => 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Case-sensitive login' => true, - 'Bind password' => 'password', - 'Login' => 'galieleo', - 'User password' => 'password' + 'Bind password' => 'Secret123', + 'Login' => 'admin', + 'User password' => 'Secret123' ], 'db_check' => [ 'authentication_type' => '1', - 'ldap_host' => 'ldap.forumsys.com', + 'ldap_host' => 'ipa.demo1.freeipa.org', 'ldap_port' => '389', - 'ldap_base_dn' => 'dc=example,dc=com', - 'ldap_bind_dn' => 'cn=read-only-admin,dc=example,dc=com', - 'ldap_bind_password' => 'password', + 'ldap_base_dn' => 'cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', + 'ldap_bind_dn' => 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', + 'ldap_bind_password' => 'Secret123', 'ldap_search_attribute' => 'uid', 'http_auth_enabled' => '0', 'http_login_form' => '0', |