You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1029 lines
25 KiB
1029 lines
25 KiB
1 year ago
|
/*
|
||
|
** 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('<div>').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 %<number>$s with arguments.
|
||
|
* Can be used like usual sprintf but only for %<number>$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('<span>')
|
||
|
.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('<ul>')
|
||
|
.addClass('list-dashed'),
|
||
|
$msg_details = jQuery('<div>')
|
||
|
.addClass('msg-details')
|
||
|
.append($list),
|
||
|
aria_labels = {good: t('Success message'), bad: t('Error message'), warning: t('Warning message')},
|
||
|
$msg_box = jQuery('<output>')
|
||
|
.addClass(classes[type]).attr('role', 'contentinfo')
|
||
|
.attr('aria-label', aria_labels[type]),
|
||
|
$details_arrow = jQuery('<span>')
|
||
|
.attr('id', 'details-arrow')
|
||
|
.addClass(show_details ? 'arrow-up' : 'arrow-down'),
|
||
|
$link_details = jQuery('<a>')
|
||
|
.text(t('Details') + ' ')
|
||
|
.addClass('link-action')
|
||
|
.attr('href', 'javascript:void(0)')
|
||
|
.attr('role', 'button')
|
||
|
.append($details_arrow)
|
||
|
.attr('aria-expanded', show_details ? 'true' : 'false');
|
||
|
|
||
|
$link_details.click(function() {
|
||
|
showHide(jQuery(this).siblings('.msg-details'));
|
||
|
jQuery('#details-arrow', jQuery(this)).toggleClass('arrow-up arrow-down');
|
||
|
jQuery(this).attr('aria-expanded', jQuery(this)
|
||
|
.find('.arrow-down')
|
||
|
.length == 0
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (title !== null) {
|
||
|
if (Array.isArray(messages) && messages.length > 0) {
|
||
|
$msg_box.prepend($link_details);
|
||
|
}
|
||
|
jQuery('<span>')
|
||
|
.text(title)
|
||
|
.appendTo($msg_box);
|
||
|
|
||
|
if (!show_details) {
|
||
|
$msg_details.hide();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Array.isArray(messages) && messages.length > 0) {
|
||
|
jQuery.map(messages, function (message) {
|
||
|
jQuery('<li>')
|
||
|
.text(message)
|
||
|
.appendTo($list);
|
||
|
return null;
|
||
|
});
|
||
|
|
||
|
$msg_box.append($msg_details);
|
||
|
}
|
||
|
|
||
|
if (show_close_box) {
|
||
|
var $button = jQuery('<button>')
|
||
|
.addClass('btn-overlay-close')
|
||
|
.attr('type', 'button')
|
||
|
.attr('title', t('Close'))
|
||
|
.click(function() {
|
||
|
jQuery(this)
|
||
|
.closest('.' + classes[type])
|
||
|
.remove();
|
||
|
});
|
||
|
$msg_box.append($button);
|
||
|
}
|
||
|
|
||
|
return $msg_box;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Download svg graph as .png image.
|
||
|
*
|
||
|
* @param {SVGElement} svg
|
||
|
* @param {string} file_name
|
||
|
*/
|
||
|
function downloadSvgImage(svg, file_name) {
|
||
|
var $dom_node = jQuery(svg),
|
||
|
canvas = document.createElement('canvas'),
|
||
|
labels = $dom_node.next('.svg-graph-legend'),
|
||
|
$clone = $dom_node.clone(),
|
||
|
$container = $dom_node.closest('.dashboard-grid-widget-contents'),
|
||
|
image = new Image,
|
||
|
a = document.createElement('a'),
|
||
|
style = document.createElementNS('http://www.w3.org/1999/xhtml', 'style'),
|
||
|
$labels_clone,
|
||
|
labels_height = labels.length ? labels.height() : 0,
|
||
|
context2d;
|
||
|
|
||
|
// Clone only svg graph styles.
|
||
|
style.innerText = jQuery.map(document.styleSheets[0].cssRules, function (rule) {
|
||
|
return rule.selectorText && rule.selectorText.substr(0, 5) == '.svg-' ? rule.cssText : '';
|
||
|
}).join('');
|
||
|
|
||
|
jQuery.map(['background-color', 'font-family', 'font-size', 'color'], function (key) {
|
||
|
if ($clone.css(key) === '') {
|
||
|
$clone.css(key, $container.css(key));
|
||
|
}
|
||
|
});
|
||
|
|
||
|
canvas.width = $dom_node.width()
|
||
|
canvas.height = $dom_node.height() + labels_height;
|
||
|
context2d = canvas.getContext('2d');
|
||
|
image.onload = function() {
|
||
|
context2d.drawImage(image, 0, 0);
|
||
|
a.href = canvas.toDataURL('image/png');
|
||
|
a.rel = 'noopener' + (ZBX_NOREFERER ? ' noreferrer' : '');
|
||
|
a.download = file_name;
|
||
|
a.target = '_blank';
|
||
|
a.click();
|
||
|
}
|
||
|
$labels_clone = jQuery(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'))
|
||
|
.attr({
|
||
|
x: 0,
|
||
|
y: canvas.height - labels_height,
|
||
|
width: canvas.width,
|
||
|
height: labels_height
|
||
|
})
|
||
|
.append(jQuery(document.createElementNS('http://www.w3.org/1999/xhtml', 'div'))
|
||
|
.append(style)
|
||
|
.append(labels.clone())
|
||
|
);
|
||
|
|
||
|
$clone.attr('height', canvas.height + 'px').append($labels_clone);
|
||
|
image.src = 'data:image/svg+xml;utf8,' + new XMLSerializer().serializeToString($clone[0]).replace(/#/g, '%23');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Download classic image as given file name.
|
||
|
*
|
||
|
* @param {HTMLImageElement} img
|
||
|
* @param {string} file_name
|
||
|
*/
|
||
|
function downloadPngImage(img, file_name) {
|
||
|
var a = document.createElement('a');
|
||
|
|
||
|
a.href = img.src;
|
||
|
a.rel = 'noopener' + (ZBX_NOREFERER ? ' noreferrer' : '');
|
||
|
a.download = file_name;
|
||
|
a.target = '_blank';
|
||
|
a.click();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes text into primary clipboard. Provides fallback for insecure context.
|
||
|
*
|
||
|
* @param {string} text Text to write.
|
||
|
*/
|
||
|
function writeTextClipboard(text) {
|
||
|
if (window.isSecureContext) {
|
||
|
return window.navigator.clipboard.writeText(text);
|
||
|
}
|
||
|
|
||
|
const textarea = document.createElement('textarea');
|
||
|
|
||
|
textarea.value = text;
|
||
|
textarea.style.position = 'fixed';
|
||
|
document.body.appendChild(textarea);
|
||
|
textarea.select();
|
||
|
document.execCommand('copy');
|
||
|
textarea.remove();
|
||
|
}
|
||
|
|
||
|
function urlEncodeData(parameters, prefix = '') {
|
||
|
const result = [];
|
||
|
|
||
|
for (let [name, value] of Object.entries(parameters)) {
|
||
|
if (value === undefined) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (value === null) {
|
||
|
value = '';
|
||
|
}
|
||
|
|
||
|
const prefixed_name = prefix !== '' ? `${prefix}[${name}]` : name;
|
||
|
|
||
|
if (Array.isArray(value) || (typeof value === 'object')) {
|
||
|
const result_part = urlEncodeData(value, prefixed_name);
|
||
|
|
||
|
if (result_part !== '') {
|
||
|
result.push(result_part);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
result.push([encodeURIComponent(prefixed_name), encodeURIComponent(value)].join('='));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result.join('&');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get form field values as deep object.
|
||
|
*
|
||
|
* Example:
|
||
|
* <form>
|
||
|
* <input name="a" value="1">
|
||
|
* <input name="b[c]" value="2">
|
||
|
* <input name="b[d]" value="3">
|
||
|
* <input name="e[f][]" value="4">
|
||
|
* <input name="e[f][]" value="5">
|
||
|
* </form>
|
||
|
*
|
||
|
* ... will result in:
|
||
|
*
|
||
|
* {
|
||
|
* a: "1",
|
||
|
* b: {
|
||
|
* c: "2",
|
||
|
* d: "3"
|
||
|
* },
|
||
|
* e: {
|
||
|
* f: ["4", "5"]
|
||
|
* }
|
||
|
* }
|
||
|
*
|
||
|
* @param {HTMLFormElement} form
|
||
|
*
|
||
|
* @return {object}
|
||
|
*/
|
||
|
function getFormFields(form) {
|
||
|
const fields = {};
|
||
|
|
||
|
for (let [key, value] of new FormData(form)) {
|
||
|
value = value.replace(/\r?\n/g, '\r\n');
|
||
|
|
||
|
const key_parts = [...key.matchAll(/[^\[\]]+|\[\]/g)];
|
||
|
|
||
|
let key_fields = fields;
|
||
|
|
||
|
for (let i = 0; i < key_parts.length; i++) {
|
||
|
const key_part = key_parts[i][0];
|
||
|
|
||
|
if (i == key_parts.length - 1) {
|
||
|
if (key_part === '[]') {
|
||
|
key_fields.push(value);
|
||
|
}
|
||
|
else {
|
||
|
key_fields[key_part] = value;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (key_part === '[]') {
|
||
|
const key_field = key_parts[i + 1][0] === '[]' ? [] : {};
|
||
|
|
||
|
key_fields.push(key_field);
|
||
|
key_fields = key_field;
|
||
|
}
|
||
|
else {
|
||
|
if (!(key_part in key_fields)) {
|
||
|
key_fields[key_part] = key_parts[i + 1][0] === '[]' ? [] : {};
|
||
|
}
|
||
|
|
||
|
key_fields = key_fields[key_part];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fields;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert RGB encoded color into HSL encoded color.
|
||
|
*
|
||
|
* @param {number} r Red component in range of 0-1.
|
||
|
* @param {number} g Green component in range of 0-1.
|
||
|
* @param {number} b Blue component in range of 0-1.
|
||
|
*
|
||
|
* @returns [{number}, {number}, {number}] Hue, saturation and lightness components.
|
||
|
*/
|
||
|
function convertRGBToHSL(r, g, b) {
|
||
|
const v = Math.max(r, g, b);
|
||
|
const c = v - Math.min(r, g, b);
|
||
|
const f = 1 - Math.abs(v * 2 - c - 1);
|
||
|
const h = c && ((v === r) ? (g - b) / c : ((v === g) ? 2 + (b - r) / c : 4 + (r - g) / c));
|
||
|
|
||
|
return [60 * (h < 0 ? h + 6 : h), f ? c / f : 0, (v * 2 - c) / 2];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert HSL encoded color into RGB encoded color.
|
||
|
*
|
||
|
* @param {number} h Hue component in range of 0-360.
|
||
|
* @param {number} s Saturation component in range of 0-1.
|
||
|
* @param {number} l Lightness component in range of 0-1.
|
||
|
*
|
||
|
* @returns [{number}, {number}, {number}] Red, green and blue components in range 0-1.
|
||
|
*/
|
||
|
function convertHSLToRGB(h, s, l) {
|
||
|
const a = s * Math.min(l, 1 - l);
|
||
|
const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||
|
|
||
|
return [f(0), f(8), f(4)];
|
||
|
}
|