/* ** 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. **/ function check_target(e, type) { // If type is expression. if (type == 0) { var targets = document.getElementsByName('expr_target_single'); } // Type is recovery expression. else { var targets = document.getElementsByName('recovery_expr_target_single'); } for (var i = 0; i < targets.length; ++i) { targets[i].checked = targets[i] == e; } } /** * Remove part of expression. * * @param string id Expression temporary ID. * @param number type Expression (type = 0) or recovery expression (type = 1). */ function delete_expression(id, type) { // If type is expression. if (type == 0) { jQuery('#remove_expression').val(id); } // Type is recovery expression. else { jQuery('#remove_recovery_expression').val(id); } } /** * Insert expression part into input field. * * @param string id Expression temporary ID. * @param number type Expression (type = 0) or recovery expression (type = 1). */ function copy_expression(id, type) { // If type is expression. if (type == 0) { var element = document.getElementsByName('expr_temp')[0]; } // Type is recovery expression. else { var element = document.getElementsByName('recovery_expr_temp')[0]; } if (element.value.length > 0 && !confirm(t('Do you wish to replace the conditional expression?'))) { return null; } var src = document.getElementById(id); if (typeof src.textContent != 'undefined') { element.value = src.textContent; } else { element.value = src.innerText; } } function testUserSound(idx) { var element = document.getElementById(idx); var sound = element.options[element.selectedIndex].value; element = document.getElementById('messages_sounds.repeat'); var repeat = element.options[element.selectedIndex].value; if (repeat == 1) { AudioControl.playOnce(sound); } else if (repeat > 1) { AudioControl.playLoop(sound, repeat); } else { AudioControl.playLoop(sound, document.getElementById('messages_timeout').value); } } /** * Converts all HTML symbols into HTML entities. */ jQuery.escapeHtml = function(html) { return jQuery('
').text(html).html(); } function validateNumericBox(obj, allowempty, allownegative) { if (obj != null) { if (allowempty) { if (obj.value.length == 0 || obj.value == null) { obj.value = ''; } else { if (isNaN(parseInt(obj.value, 10))) { obj.value = 0; } else { obj.value = parseInt(obj.value, 10); } } } else { if (isNaN(parseInt(obj.value, 10))) { obj.value = 0; } else { obj.value = parseInt(obj.value, 10); } } } if (!allownegative) { if (obj.value < 0) { obj.value = obj.value * -1; } } } /** * Translates the given string. * * @param {String} str */ function t(str) { return (!!locale[str]) ? locale[str] : str; } /** * Generates unique id with prefix 'new'. * id starts from 0 in each JS session. * * @return string */ function getUniqueId() { if (typeof getUniqueId.id === 'undefined') { getUniqueId.id = 0; } return 'new' + (getUniqueId.id++).toString(); } /** * Color palette object used for getting different colors from color palette. */ let colorPalette = (function() { 'use strict'; let palette = []; return { /** * Gets next color from palette. * * @param {array} used_colors Array of already used hexadecimal color codes. * * @return string Hexadecimal color code. */ getNextColor: function(used_colors) { if (!used_colors.length) { return palette[0] || ''; } const palette_usage = {}; for (const color of palette) { palette_usage[color] = used_colors.filter(used_color => used_color === color).length; } const min_used_color_count = Math.min(...Object.values(palette_usage)); return Object.keys(palette_usage).find(color => palette_usage[color] == min_used_color_count); }, /** * Set color palette. * * @param {array} colors Array of hexadecimal color codes. */ setThemeColors: function(colors) { palette = colors; } } }()); /** * Returns the number of properties of an object. * * @param obj * * @return int */ function objectSize(obj) { var size = 0, key; for (key in obj) { if (obj.hasOwnProperty(key)) { size++; } } return size; } /** * Add standard message to the top of the site. * * @param {jQuery} jQuery object representing HTML message box with class name .msg-good, .msg-bad or .msg-warning. */ function addMessage($msg_box) { var $wrapper = $('.wrapper'), $main = $wrapper.find('> main'), $footer = $wrapper.find('> footer'); if ($main.length) { $main.before($msg_box); } else if ($footer.length) { $footer.before($msg_box); } else { $wrapper.append($msg_box); } } /** * Clear standard messages. */ function clearMessages() { $('.wrapper').find('> .msg-good, > .msg-bad, > .msg-warning').not('.msg-global-footer').remove(); } /** * Prepare Ok message for displaying after page reload. * * @param {String} message */ function postMessageOk(message) { cookie.create('system-message-ok', message); } /** * Prepare Error message for displaying after page reload. * * @param {String} message */ function postMessageError(message) { cookie.create('system-message-error', message); } function postMessageDetails(type, messages) { const encode = function (string) { const uint8 = new TextEncoder().encode(string); let result = ''; for (let i = 0; i < uint8.byteLength; i++) { result += String.fromCharCode(uint8[i]); } return result; }; const data = JSON.stringify({ type: type, messages: messages }); cookie.create('system-message-details', btoa(encode(data))); } /** * Replace placeholders like %$s with arguments. * Can be used like usual sprintf but only for %$s placeholders. * * @param string * * @return string */ function sprintf(string) { var placeHolders, position, replace; if (typeof string !== 'string') { throw Error('Invalid input type. String required, got ' + typeof string); } placeHolders = string.match(/%\d\$[sd]/g); for (var l = placeHolders.length - 1; l >= 0; l--) { position = placeHolders[l][1]; replace = arguments[position]; if (typeof replace === 'undefined') { throw Error('Placeholder for non-existing parameter'); } string = string.replace(placeHolders[l], replace) } return string; } /** * Optimization: * * 86400 = 24 * 60 * 60 * 31536000 = 365 * 86400 * 2592000 = 30 * 86400 * 604800 = 7 * 86400 * * @param int timestamp * @param bool isTsDouble * @param bool isExtend * * @return string */ function formatTimestamp(timestamp, isTsDouble, isExtend) { timestamp = timestamp || 0; var years = 0, months = 0; if (isExtend) { years = Math.floor(timestamp / 31536000); months = Math.floor((timestamp - years * 31536000) / 2592000); } var days = Math.floor((timestamp - years * 31536000 - months * 2592000) / 86400), hours = Math.floor((timestamp - years * 31536000 - months * 2592000 - days * 86400) / 3600), minutes = Math.floor((timestamp - years * 31536000 - months * 2592000 - days * 86400 - hours * 3600) / 60); // due to imprecise calculations it is possible that the remainder contains 12 whole months but no whole years if (months == 12) { years++; months = 0; } if (isTsDouble) { if (months.toString().length == 1) { months = '0' + months; } if (days.toString().length == 1) { days = '0' + days; } if (hours.toString().length == 1) { hours = '0' + hours; } if (minutes.toString().length == 1) { minutes = '0' + minutes; } } var str = (years == 0) ? '' : years + t('S_YEAR_SHORT') + ' '; str += (months == 0) ? '' : months + t('S_MONTH_SHORT') + ' '; str += (isExtend && isTsDouble) ? days + t('S_DAY_SHORT') + ' ' : ((days == 0) ? '' : days + t('S_DAY_SHORT') + ' '); str += (hours == 0) ? '' : hours + t('S_HOUR_SHORT') + ' '; str += (minutes == 0) ? '' : minutes + t('S_MINUTE_SHORT') + ' '; return str; } /** * Splitting string using slashes with escape backslash support. * * @param string $path * * @return array */ function splitPath(path) { var items = [], s = '', escapes = ''; for (var i = 0, size = path.length; i < size; i++) { if (path[i] === '/') { if (escapes === '') { items[items.length] = s; s = ''; } else { if (escapes.length % 2 == 0) { s += stripslashes(escapes); items[items.length] = s; s = escapes = ''; } else { s += stripslashes(escapes) + path[i]; escapes = ''; } } } else if (path[i] === '\\') { escapes += path[i]; } else { s += stripslashes(escapes) + path[i]; escapes = ''; } } if (escapes !== '') { s += stripslashes(escapes); } items[items.length] = s; return items; } /** * Removing unescaped backslashes from string. * Analog of PHP stripslashes(). * * @param string str * * @return string */ function stripslashes(str) { return str.replace(/\\(.?)/g, function(s, chars) { if (chars == '\\') { return '\\'; } else if (chars == '') { return ''; } else { return chars; } }); } /** * Function to remove preloader and moves focus to IU element that was clicked to open it. * * @param string id Preloader identifier. */ function overlayPreloaderDestroy(id) { if (typeof id !== 'undefined') { var overlay = overlays_stack.getById(id) if (!overlay) { return; } if (typeof overlay.xhr !== 'undefined') { overlay.xhr.abort(); delete overlay.xhr; } jQuery('#' + id).remove(); removeFromOverlaysStack(id); } } /** * Function to close overlay dialogue and moves focus to IU element that was clicked to open it. * * @param string dialogueid Dialogue identifier to identify dialogue. */ function overlayDialogueDestroy(dialogueid) { if (typeof dialogueid !== 'undefined') { var overlay = overlays_stack.getById(dialogueid) if (!overlay) { return; } if (typeof overlay.xhr !== 'undefined') { overlay.xhr.abort(); delete overlay.xhr; } if (overlay instanceof Overlay) { overlay.unmount(); } jQuery('[data-dialogueid='+dialogueid+']').remove(); removeFromOverlaysStack(dialogueid); overlay.$dialogue[0].dispatchEvent(new CustomEvent('dialogue.close', {detail: {dialogueid}})); } } /** * Display modal window. * * @param {object} params Modal window params. * @param {string} params.title Modal window title. * @param {string} params.class Modal window CSS class, often based on .modal-popup*. * @param {object} params.content Window content. * @param {object} params.footer Window footer content. * @param {object} params.controls Window controls. * @param {array} params.buttons Window buttons. * @param {string} params.debug Debug HTML displayed in modal window. * @param {string} params.buttons[]['title'] Text on the button. * @param {object}|{string} params.buttons[]['action'] Function object or executable string that will be executed * on click. * @param {string} params.buttons[]['class'] (optional) Button class. * @param {bool} params.buttons[]['cancel'] (optional) It means what this button has cancel action. * @param {bool} params.buttons[]['focused'] (optional) Focus this button. * @param {bool} params.buttons[]['enabled'] (optional) Should the button be enabled? Default: true. * @param {bool} params.buttons[]['keepOpen'] (optional) Prevent dialogue closing, if button action returned false. * @param string params.dialogueid (optional) Unique dialogue identifier to reuse existing overlay dialog * or create a new one if value is not set. * @param string params.script_inline (optional) Custom javascript code to execute when initializing dialog. * @param {Node|null} trigger_elmnt UI element which triggered opening of overlay dialogue. * * @return {Overlay} */ function overlayDialogue(params, trigger_elmnt) { params.element = params.element || trigger_elmnt; params.type = params.type || 'popup'; var overlay = overlays_stack.getById(params.dialogueid); if (!overlay) { overlay = new Overlay(params.type, params.dialogueid); } overlay.setProperties(params); overlay.mount(); overlay.recoverFocus(); overlay.containFocus(); addToOverlaysStack(overlay); return overlay; } /** * Execute script. * * @param string scriptid Script ID. * @param string confirmation Confirmation text. * @param {Node} trigger_element UI element that was clicked to open overlay dialogue. * @param string hostid Host ID. * @param string eventid Event ID. * @param string csrf_token CSRF token. */ function executeScript(scriptid, confirmation, trigger_element, hostid = null, eventid = null, csrf_token) { var execute = function() { var popup_options = {scriptid: scriptid}; if (hostid !== null) { popup_options.hostid = hostid; } if (eventid !== null) { popup_options.eventid = eventid; } if (Object.keys(popup_options).length === 2) { popup_options._csrf_token = csrf_token; PopUp('popup.scriptexec', popup_options, {dialogue_class: 'modal-popup-medium', trigger_element}); } }; if (confirmation.length > 0) { overlayDialogue({ 'title': t('Execution confirmation'), 'content': jQuery('') .addClass('confirmation-msg') .text(confirmation), 'class': 'modal-popup modal-popup-small position-middle', 'buttons': [ { 'title': t('Cancel'), 'class': 'btn-alt', 'focused': (hostid === null && eventid === null), 'action': function() {} }, { 'title': t('Execute'), 'enabled': (hostid !== null || eventid !== null), 'focused': (hostid !== null || eventid !== null), 'action': function() { execute(); } } ] }, trigger_element); return false; } else { execute(); } } (function($) { $.fn.serializeJSON = function() { var json = {}; jQuery.map($(this).serializeArray(), function(n) { var l = n['name'].indexOf('['), r = n['name'].indexOf(']'), curr_json = json; if (l != -1 && r != -1 && r > l) { var key = n['name'].substr(0, l); if (l + 1 == r) { if (typeof curr_json[key] === 'undefined') { curr_json[key] = []; } curr_json[key].push(n['value']); } else { if (typeof curr_json[key] === 'undefined') { curr_json[key] = {}; } curr_json = curr_json[key]; do { key = n['name'].substr(l + 1, r - l - 1); l = n['name'].indexOf('[', r + 1); r = n['name'].indexOf(']', r + 1); if (l + 1 == r) { if (typeof curr_json[key] === 'undefined') { curr_json[key] = []; } curr_json[key].push(n['value']); break; } else if (l == -1 || r == -1 || r <= l) { curr_json[key] = n['value'] break; } if (typeof curr_json[key] === 'undefined') { curr_json[key] = {}; } curr_json = curr_json[key]; } while (l != -1 && r != -1 && r > l); } } else { json[n['name']] = n['value']; } }); return json; }; })(jQuery); /** * Parse URL string to object. Hash starting part of URL will be removed. * Return object where 'url' key contains parsed URL, 'pairs' key is array of objects with parsed arguments. * For malformed URL strings will return false. * * @param {string} url_string URL string to parse. * * @return {object|bool} */ function parseUrlString(url_string) { try { decodeURI(url_string); } catch { return false; } let url = url_string.replace(/#.+/, ''); const pos = url.indexOf('?'); const pairs = []; if (pos != -1) { const query = url.substring(pos + 1); url = url.substring(0, pos); for (const param of new URLSearchParams(query)) { if (encodeURIComponent(param[0]).match(/%[01]/) || encodeURIComponent(param[1]).match(/%[01]/)) { // Non-printable characters in URL. return false; } pairs.push({ 'name': param[0], 'value': param[1] }); } } return { 'url': url, 'pairs': pairs }; } /** * Message formatting function. * * @param {string} type Message type. ('good'|'bad'|'warning') * @param {array} messages Error messages. * @param {string|null} title Error title. * @param {boolean} show_close_box Show close button. * @param {boolean|null} show_details Show details on opening. * * @return {jQuery} */ function makeMessageBox(type, messages, title = null, show_close_box = true, show_details = null) { const classes = { good: 'msg-good', bad: 'msg-bad', warning: 'msg-warning' }; if (show_details === null) { show_details = type === 'bad' || type === 'warning'; } var $list = jQuery('