diff --git a/.github/workflows/develop-client-security.yml b/.github/workflows/develop-client-security.yml new file mode 100644 index 000000000..55d964aac --- /dev/null +++ b/.github/workflows/develop-client-security.yml @@ -0,0 +1,30 @@ +name: Develop client security +on: + pull_request: + branches: [develop] + push: + branches: [develop] +permissions: + contents: read +jobs: + chromium: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version: '22' + cache: npm + cache-dependency-path: tests/e2e/package-lock.json + - name: Install locked browser tooling + working-directory: tests/e2e + run: | + set -euo pipefail + npm ci --ignore-scripts + npx --no-install playwright install --with-deps chromium + - name: Verify popup isolation, navigation state and column widths + working-directory: tests/e2e + run: npx --no-install playwright test tests/develop-client-security.spec.ts tests/develop-menu-state.spec.ts tests/table-column-widths.spec.ts --project=chromium diff --git a/include/js/navigationBox.tableLayout.js b/include/js/navigationBox.tableLayout.js index d1a9b3e5a..8ea709474 100644 --- a/include/js/navigationBox.tableLayout.js +++ b/include/js/navigationBox.tableLayout.js @@ -261,7 +261,7 @@ midwinter.navigationBox.table = { */ init: function($box) { if (!$box || !$box.length) return; - const helper = $box.data('helper'); + $box.data('helper'); const ns = midwinter.navigationBox.table; // bind toggle checkboxes diff --git a/include/js/navigationBox.tree.js b/include/js/navigationBox.tree.js index 9f9d8d7b6..125c8affb 100644 --- a/include/js/navigationBox.tree.js +++ b/include/js/navigationBox.tree.js @@ -34,7 +34,7 @@ midwinter.navigationBox.tree = { */ getDefaultConfig: function(overrides = {}) { // derive the type from the object path - const type = 'tree'; + const base = 'midwinter.navigationBox'; const defaults = { diff --git a/include/js/ui-notices.js b/include/js/ui-notices.js index 815746347..f704cfc7a 100644 --- a/include/js/ui-notices.js +++ b/include/js/ui-notices.js @@ -16,15 +16,15 @@ $.toastPopup = function(options) { loader: true, } - if (typeof options == "undefined") { + if (options === undefined) { options = {}; - } else if (typeof options == "string" || typeof options == "String") { + } else if (typeof options === "string" || options instanceof String) { options = { - text: options + text: String(options) }; } - toastOptions = $.extend(defaultOptions, options, fixedOptions); + const toastOptions = $.extend(defaultOptions, options, fixedOptions); return $.toast(toastOptions); } diff --git a/include/layout.js b/include/layout.js index be1a969f7..6999d6739 100644 --- a/include/layout.js +++ b/include/layout.js @@ -199,7 +199,7 @@ jQuery.ajaxQ = (function () { */ function basename(path, suffix) { var b = path; - var lastChar = b.charAt(b.length - 1); + b.charAt(b.length - 1); // Don't remove trailing slash for now // /* @@ -229,12 +229,12 @@ function basename(path, suffix) { /** getTimestampFromDate - Simple function to convert a MySQL Date * to a timestamp */ function getTimestampFromDate(dateStamp) { - if (typeof dateStamp != 'undefined') { + if (dateStamp !== undefined) { var dateParts = dateStamp.split(' '); var timeParts = dateParts[1].split(':'); dateParts = dateParts[0].split('-'); - var date = new Date(dateParts[0], parseInt(dateParts[1], 10) - 1, dateParts[2], timeParts[0], timeParts[1]); + var date = new Date(dateParts[0], Number.parseInt(dateParts[1], 10) - 1, dateParts[2], timeParts[0], timeParts[1]); return date.getTime() / 1000; } @@ -267,7 +267,7 @@ function getQueryString(name) { */ $.fn.delayKeyup = function (callback, ms) { var timer = 0; - if (typeof ms === 'undefined' || ms < 500) { + if (ms === undefined || ms < 500) { ms = keyup_delay; } @@ -388,7 +388,7 @@ $.fn.enableDisableOptions = function (values, optionEnabled, valueCheckFunc) { } var selectMenu = $(this).selectmenu('instance'); - if (typeof selectMenu != 'undefined') { + if (selectMenu !== undefined) { $(this).selectmenu('refresh'); } @@ -448,11 +448,12 @@ $.fn.textWidth = function (text) { * without any wrapping. */ $.fn.textBoxWidth = function () { + var text; var org = $(this); if (org.val()) { - var text = encodeURIComponent(org.val()); + text = encodeURIComponent(org.val()); } else { - var text = encodeURIComponent(org.text()); + text = encodeURIComponent(org.text()); } var html = $('' + text + ''); @@ -842,7 +843,7 @@ function dqUpdateDeps(snmp_query_id) { $('tr[id^="dqline' + snmp_query_id + '_"]').each(function (data) { var id = $(this).attr('id'); var pieces = id.split('_'); - var dq = pieces[0].substr(6); + pieces[0].substr(6); var hash = pieces[1]; if ($.inArray(hash, created_graphs[snmp_query_graph_id]) >= 0) { @@ -1104,12 +1105,13 @@ function applySkin() { $(document).tooltip({ items: 'div.cactiTooltipHint, span.cactiTooltipHint, .checkboxSlider', content: function () { + var text; var element = $(this); if (element.is('div')) { - var text = DOMPurify.sanitize($(this).find('span').html()); + text = DOMPurify.sanitize($(this).find('span').html()); } else if (element.is('span') || element.is('a')) { - var text = DOMPurify.sanitize($(this).prop('title')); + text = DOMPurify.sanitize($(this).prop('title')); } return text; } @@ -1171,7 +1173,7 @@ function applySkin() { $('#main').css('display', 'table'); - var showPage = $('#main').map(function (i, el) { + $('#main').map(function (i, el) { var dfd = $.Deferred(); $(el).show(function () { dfd.resolve(); @@ -1569,19 +1571,19 @@ function sessionNoticesDisplay() { var title = ''; var id = ''; - if (typeof notice.message !== 'undefined') { + if (notice.message !== undefined) { message = notice.message; } - if (typeof notice.title !== 'undefined') { + if (notice.title !== undefined) { titLe = notice.tite; } - if (typeof notice.level !== 'undefined') { + if (notice.level !== undefined) { level = notice.level; } - if (typeof notice.id !== 'undefined') { + if (notice.id !== undefined) { id = '-' + notice.id; } @@ -1617,6 +1619,7 @@ function sessionNoticesDisplay() { } function sessionMessageDisplay() { + var sessionMessageButtons; var title = ''; var header = ''; @@ -1628,11 +1631,11 @@ function sessionMessageDisplay() { return; } - if (typeof sessionMessage.level != 'undefined') { + if (sessionMessage.level !== undefined) { if (sessionMessage.level == MESSAGE_LEVEL_ERROR) { title = errorReasonTitle; header = errorOnPage; - var sessionMessageButtons = { + sessionMessageButtons = { 'Ok': { text: sessionMessageOk, id: 'btnSessionMessageOk', @@ -1646,7 +1649,7 @@ function sessionMessageDisplay() { } else if (sessionMessage.level == MESSAGE_LEVEL_MIXED) { title = mixedReasonTitle; header = mixedOnPage; - var sessionMessageButtons = { + sessionMessageButtons = { 'Ok': { text: sessionMessageOk, id: 'btnSessionMessageOk', @@ -1663,7 +1666,7 @@ function sessionMessageDisplay() { } else { title = sessionMessageTitle; header = sessionMessageSave; - var sessionMessageButtons = { + sessionMessageButtons = { 'Pause': { text: sessionMessagePause, id: 'btnSessionMessagePause', @@ -2006,10 +2009,11 @@ function responsiveUI(event) { /* change textbox and textarea widths */ $('input[type="text"], textarea').each(function () { + var offset; if ($(this).attr('type') == 'text') { - var offset = 20; + offset = 20; } else { - var offset = 5; + offset = 5; } if (mainWidth != 100) { @@ -2039,11 +2043,12 @@ function responsiveUI(event) { } function getMainWidth() { + var mainWidth; // Subtract 15px for the scroll bar if ($('#navigation').length && $('#navigation').is(':visible')) { - var mainWidth = $('body').outerWidth() - $('#navigation').width() - 15; + mainWidth = $('body').outerWidth() - $('#navigation').width() - 15; } else { - var mainWidth = $('body').outerWidth() - 15; + mainWidth = $('body').outerWidth() - 15; } return mainWidth; @@ -2060,21 +2065,20 @@ function getCactiHelp(cactiPage) { } function responsiveResizeGraphs(initialize) { + var graphRow, drillDown; var mainWidth = getMainWidth() - 30; var myColumns = $('#columns').val(); - var isThumb = $('#thumbnails').is(':checked') || $('#thumbnails').val() == 'true'; - var isBusiness = $('#business_hours').is(':checked') || $('#business_hours').val() == 'true'; if ($('.tableRowGraph:first').length) { - var graphRow = $('.tableRowGraph:first').width(); + graphRow = $('.tableRowGraph:first').width(); } else { - var graphRow = 0; + graphRow = 0; } if ($('.graphDrillDown:first').length) { - var drillDown = $('.graphDrillDown:first').outerWidth() + 15; + drillDown = $('.graphDrillDown:first').outerWidth() + 15; } else { - var drillDown = 0; + drillDown = 0; } if (myColumns == null) { @@ -2088,12 +2092,12 @@ function responsiveResizeGraphs(initialize) { } // Dont resize if nothing changed - if (typeof initialize == 'undefined' && (previousMainWidth == null || (previousMainWidth == mainWidth && previousColumns == myColumns))) { + if (initialize === undefined && (previousMainWidth == null || (previousMainWidth == mainWidth && previousColumns == myColumns))) { previousMainWidth = mainWidth; return true; } - var myWidth = parseInt((graphRow - (drillDown * myColumns)) / myColumns); + var myWidth = Number.parseInt((graphRow - (drillDown * myColumns)) / myColumns); $('.graphimage').each(function () { var graph_id = $(this).attr('graph_id'); @@ -2103,11 +2107,11 @@ function responsiveResizeGraphs(initialize) { graph_id = $(this).attr('id').replace('graph_', ''); } - var rra_id = $(this).attr('rra_id'); + $(this).attr('rra_id'); var type = $(this).attr('graph_type'); var original_cwidth = $('#wrapper_' + graph_id).attr('graph_width'); - var original_cheight = $('#wrapper_' + graph_id).attr('graph_height'); + $('#wrapper_' + graph_id).attr('graph_height'); /* original image attributes */ var image_width = $(this).attr('image_width'); @@ -2131,12 +2135,12 @@ function responsiveResizeGraphs(initialize) { ratio = 1; } - var new_image_width = parseInt(image_width * ratio) - var new_image_height = parseInt(image_height * ratio) - var new_canvas_width = parseInt(canvas_width * ratio); - var new_canvas_height = parseInt(canvas_height * ratio); - var new_canvas_graph_top = parseInt(canvas_top * ratio); - var new_canvas_graph_left = parseInt(canvas_left * ratio); + var new_image_width = Number.parseInt(image_width * ratio) + var new_image_height = Number.parseInt(image_height * ratio) + var new_canvas_width = Number.parseInt(canvas_width * ratio); + var new_canvas_height = Number.parseInt(canvas_height * ratio); + var new_canvas_graph_top = Number.parseInt(canvas_top * ratio); + var new_canvas_graph_left = Number.parseInt(canvas_left * ratio); $(this).attr('graph_width', new_canvas_width); $(this).attr('graph_height', new_canvas_height); @@ -2163,8 +2167,9 @@ function responsiveResizeGraphs(initialize) { } function resizeTreePanel() { + var navWidth; var docHeight = $(window).outerHeight(); - var navWidth = $('.cactiTreeNavigationArea').width(); + navWidth = $('.cactiTreeNavigationArea').width(); var searchHeight = $('.cactiTreeSearch').outerHeight(); var pageHeadHeight = $('.cactiPageHead').outerHeight(); var breadCrHeight = $('.breadCrumbBar').outerHeight(); @@ -2214,7 +2219,7 @@ function resizeTreePanel() { $('.cactiTreeNavigationArea').css('overflow-x', ''); } - var navWidth = $('#navigation').width(); + navWidth = $('#navigation').width(); $('#searcher').css('width', getTreeSearchWidth(navWidth)); } @@ -2279,10 +2284,10 @@ function tuneTable(object, width) { var width = width; var tableWidth = $(object).width(); var totalCols = $(object).find('th').length; - var reducedWidth = 0; + var columnsHidden = countHiddenCols(object); var visibleColumns = totalCols - columnsHidden; - var id = $(object).attr('id'); + $(object).attr('id'); if (rows > 101) return false; @@ -2306,7 +2311,7 @@ function tuneTable(object, width) { var debug = false; var tableHeaders = $(object).find('th'); - var tableCheckBox = $(tableHeaders).each(function () { + $(tableHeaders).each(function () { if ($(this).index() == tableHeaders.length) { calculatedColumns.addClass('noHide'); } @@ -2343,7 +2348,7 @@ function tuneTable(object, width) { // and See which need to be hidden if (!calculatedColumns.includes($(this).index())) { var columnWidth = $.textMetrics(this).width + calculatedPadding; - var name = $(this).find('div.sortinfo').attr('sort-column'); + $(this).find('div.sortinfo').attr('sort-column'); if (debug) { console.log( @@ -2376,7 +2381,6 @@ function tuneTable(object, width) { tableChanged = false; if (allSeenWidth < width) { - calculatedColumns = calculatedColumns.sort(); // Since we can show hidden columns now, let's go // in reverse until we run out of space @@ -2384,7 +2388,7 @@ function tuneTable(object, width) { if (!calculatedColumns.includes($(this).index())) { if ($(this).is(':hidden') && stopExpand == false) { var columnWidth = $.textMetrics(this).width + calculatedPadding; - var name = $(this).find('div.sortinfo').attr('sort-column'); + $(this).find('div.sortinfo').attr('sort-column'); if (allSeenWidth + columnWidth < tableWidth) { $(this).show(); @@ -2424,17 +2428,18 @@ function tuneTable(object, width) { } function tuneFilter(object, width) { + var timespan, clearWidth; if ($(object).find('#timespan').length && $(object).find('#timespan').is(':visible')) { - var timespan = true; + timespan = true; var timeShiftWidth = $(object).find('.shiftArrow').closest('td').width(); var dateWidth = $(object).find('#date1').closest('td').width() + $(object).find('#date1').closest('td').prev('td').width(); - var clearWidth = $('#tsclear').width(); + clearWidth = $('#tsclear').width(); var refreshWidth = $('#tsrefresh').width(); } else { - var timespan = false; + timespan = false; - var clearWidth = $(object).find('#clear').width(); + clearWidth = $(object).find('#clear').width(); var saveWidth = $(object).find('#save').width(); var exportWidth = $(object).find('#export').width(); var importWidth = $(object).find('#import').width(); @@ -2573,7 +2578,7 @@ function tuneFilter(object, width) { function handleUserMenu(toggle) { var storage = Storages.sessionStorage; - var windowWidth = $(window).width(); + $(window).width(); /* set the navigation id is not set */ if (currentNavId == 'undefined' || currentNavId == null) { @@ -2610,7 +2615,7 @@ function handleUserMenu(toggle) { function menuHide(store) { var myClass = ''; - var curMargin = parseInt($('#navigation_right').css('margin-left')); + var curMargin = Number.parseInt($('#navigation_right').css('margin-left')); if ($('.cactiTreeNavigationArea').length) { myClass = '.cactiTreeNavigationArea'; @@ -2691,7 +2696,7 @@ function loadTopTab(href, id, force) { var storage = Storages.sessionStorage; /* used for debugging */ - var stack = ''; //getStackTrace(); // new Error().stack; + //getStackTrace(); // new Error().stack; var url = href;// + (href.indexOf('?') > 0 ? '&' : '?') + 'headercontent=true'; /* close all toasts */ @@ -2788,8 +2793,6 @@ function userMenuNavigationExists(url) { return myFound; } - - return false; } function loadPageUsingPost(href, postData, returnLocation) { @@ -2942,7 +2945,7 @@ function loadUrl(options) { /* get rid of old selectmenu's */ $('.ui-selectmenu-menu').empty(); - if (typeof options.noState == 'undefined' || options.noState == false) { + if (options.noState === undefined || options.noState == false) { /* close all toasts */ $.toast().reset('all'); } @@ -3008,7 +3011,7 @@ function postUrl(options, data) { statePushed = false; cont = false; - if (typeof options.noState == 'undefined' || options.noState == false) { + if (options.noState === undefined || options.noState == false) { /* close all toasts */ $.toast().reset('all'); } @@ -3035,7 +3038,7 @@ function findElement(htmlObject, element) { if (htmlObject !== null) { wanted = htmlObject.find(element); - if (typeof wanted == 'undefined' || wanted.length == 0) { + if (wanted === undefined || wanted.length == 0) { wanted = htmlObject.filter(element); } } @@ -3060,7 +3063,7 @@ function sanitizeAjaxOptions(check) { redirect: '', }; - if (typeof check == 'undefined') { + if (check === undefined) { check = {}; } @@ -3071,11 +3074,11 @@ function sanitizeAjaxOptions(check) { options.noState = options.url.indexOf('nostate') > -1; } - if (typeof check.noState != 'undefined') { + if (check.noState !== undefined) { options.noState = check.noState; } - if (typeof check.scroll != 'undefined') { + if (check.scroll !== undefined) { options.scroll = check.scroll; } @@ -3143,7 +3146,7 @@ function handleAjaxResponse(html, options) { } var matches = null; - if (typeof html.match !== 'undefined') { + if (html.match !== undefined) { matches = html.match(/(.*?)<\/title>/); } @@ -3158,7 +3161,7 @@ function handleAjaxResponse(html, options) { myTitle = htmlTitle; } - if (typeof htmlContent == 'undefined') { + if (htmlContent === undefined) { htmlContent = html; } @@ -3172,7 +3175,7 @@ function handleAjaxResponse(html, options) { let confirmation = '#action_confirm'; let htmlConfirm = findElement(htmlObject, confirmation).parent().html(); - if (typeof htmlConfirm !== 'undefined' && typeof window['preparePopOver'] === 'function') { + if (htmlConfirm !== undefined && typeof window['preparePopOver'] === 'function') { preparePopOver(htmlConfirm); }else { $(elementId).hide().empty().hide(); @@ -3217,7 +3220,7 @@ function handleAjaxResponse(html, options) { } if (options.funcEnd != '') { - if (typeof window[options.funcEnd] === 'undefined') { + if (window[options.funcEnd] === undefined) { console.log('WARNING: Missing function "' + options.funcEnd + '".'); } else { window[options.funcEnd](options, html); @@ -3236,13 +3239,14 @@ function getPresentHTTPError(data) { } function getPresentHTTPErrorOrRedirect(data, url) { - if (typeof data.status != 'undefined') { + var errorSub, errorText; + if (data.status !== undefined) { var errorStr = data.status; - var errorSub = data.statusText; - var errorText = errorReasonUnexpected; + errorSub = data.statusText; + errorText = errorReasonUnexpected; var found = false; - if (typeof data.responseText != 'undefined') { + if (data.responseText !== undefined) { var dataText = data.responseText; var title_match = dataText.match(/<title>(.*?)<\/title>/); @@ -3250,22 +3254,22 @@ function getPresentHTTPErrorOrRedirect(data, url) { var para_match = dataText.match(/<p>(.*?)<\/p>/); if (title_match != null) { - var errorSub = title_match[1]; + errorSub = title_match[1]; found = true; } if (head_match != null) { - var errorSub = head_match[1]; + errorSub = head_match[1]; found = true; } if (para_match != null) { - var errorText = para_match[1]; + errorText = para_match[1]; found = true; } if (!found && dataText != '') { - var errorText = dataText; + errorText = dataText; } var returnStr = '<div id="httperror" style="display:none">' + @@ -3299,7 +3303,7 @@ function getPresentHTTPErrorOrRedirect(data, url) { } } - if (typeof url != 'undefined') { + if (url !== undefined) { if (data.status >= 500) { // Let the HTTP Error stick log an error $.get(urlPath + 'help.php?page=' + encodeURIComponent(url) + '&error=' + data.status); @@ -3313,7 +3317,7 @@ function getPresentHTTPErrorOrRedirect(data, url) { } function ajaxAnchors() { - var page = basename(location.pathname); + basename(location.pathname); $(document).rebind('click.cactiPostAction', 'a.cactiPostAction', function (event) { if (!shouldCaptureClick(event)) { @@ -3389,11 +3393,11 @@ function checkFormStatus(href, type, scroll_or_id) { var formID = $(this).attr('id'); var submitData = $(this).serializeForm(); - if (typeof formArray != 'undefined' && typeof formArray[formID] != 'undefined') { + if (typeof formArray != 'undefined' && formArray[formID] !== undefined) { var formData = formArray[formID]; $.each(submitData, function (index, value) { - if (typeof formData[index] != 'undefined') { + if (formData[index] !== undefined) { if (formData[index] != value) { if (index == 'settings_sendmail_path' || index == 'rrd_archive' || index == '__csrf_magicSubmit' || index == '__csrf_magic' || index == 'settings_smtp_password' || index == 'settings_smtp_password_confirm') { // Ignore this entry @@ -3424,11 +3428,12 @@ function setupCollapsible() { var storage = Storages.sessionStorage; $('.collapsible').each(function (data) { + var state; var id = $(this).attr('id') + '_cs'; if (storage.isSet(id)) { - var state = storage.get(id); + state = storage.get(id); } else { - var state = 'show'; + state = 'show'; } if (state == 'hide') { @@ -3541,7 +3546,7 @@ function setupSortable() { var $target = $(e.target); var sortAdd = ''; - var href = ''; + if (!$target.is('.ui-resizable-handle')) { var page = $(this).find('.sortinfo').attr('sort-page'); @@ -3598,10 +3603,11 @@ function setupBreadcrumbs() { function saveTableWidths(initial) { // We will save columns widths persistently var storage = Storages.localStorage; - var key; + // Initialize table width on the page $('table.cactiTable').each(function (data) { + var sizes, items; var table = $(this); // Persist widths under the action-independent data-colsizing key when it @@ -3615,27 +3621,27 @@ function saveTableWidths(initial) { } if (storage.isSet(key)) { - var sizes = storage.get(key); + sizes = storage.get(key); } else { - var sizes = new Array(); + sizes = new Array(); } - var items = sizes ? sizes : new Array(); + items = sizes ? sizes : new Array(); var width = $(document).width(); // if the table width changes, reset the columns if (key !== undefined && sizes == undefined) { storage.remove(key); - var sizes = new Array(); - var items = new Array(); + sizes = new Array(); + items = new Array(); } else if (key !== undefined && initial) { if (items.length > 0) { if (items[0] + 18 < width) { storage.remove(key); - var sizes = new Array(); - var items = new Array(); + sizes = new Array(); + items = new Array(); } } } @@ -3912,7 +3918,7 @@ function setSelectMenus() { that._renderItemData( ul, item ); }); - if (typeof(attr['data-defaultLabel']) !== 'undefined') { + if (attr['data-defaultLabel'] !== undefined) { if(this.menuWrap.find('input').length === 0) { this.menuWrap.prepend('<div class="ui-selectmenu-search"><input type="search" class="ui-state-default ui-corner-all" data-scope="theme" placeholder="' + searchPlaceholder + '"></div>'); } @@ -3957,17 +3963,17 @@ function setSelectMenus() { } }, _renderButtonItem: function(item) { - let that = this; + let attr = this.element[0].attributes; let showDefaultLabel = select2Setup["displayDefaultLabel"]; - if (typeof(attr['data-defaultLabel']) !== 'undefined') { + if (attr['data-defaultLabel'] !== undefined) { let defaultLabel = attr['data-defaultLabel'].value; let filterActive = ''; let defaultIndex = 0; let defaultValue = this.element.find("option").eq(0).val(); - if (typeof(attr['data-defaultValue']) !== 'undefined') { + if (attr['data-defaultValue'] !== undefined) { let defaultValue = attr['data-defaultValue'].value; } @@ -4019,6 +4025,7 @@ function setSelectMenus() { $.widget('custom.dropicon', $.ui.selectmenu, { _renderItem: function( ul, item ) { + var style; var li = $('<li>'); var wrapper = $('<div>', {text: item.label}); @@ -4027,9 +4034,9 @@ function setSelectMenus() { } if (item.element.attr('data-style') != '') { - var style = item.element.attr('data-style')+';background:transparent;min-width:20px;'; + style = item.element.attr('data-style')+';background:transparent;min-width:20px;'; } else { - var style = 'background:transparent;min-width:20px;'; + style = 'background:transparent;min-width:20px;'; } var iclass = item.element.attr('data-class'); @@ -4273,10 +4280,10 @@ function setupEllipsis() { /* re-position */ var position = $(this).position(); - if (position.left - parseInt(submenu.outerWidth()) < 0) { + if (position.left - Number.parseInt(submenu.outerWidth()) < 0) { submenu.css({ 'left': 0 }).slideDown(120); } else { - submenu.css({ 'left': position.left - parseInt(submenu.outerWidth()) + parseInt($(this).outerWidth()) }).slideDown(120); + submenu.css({ 'left': position.left - Number.parseInt(submenu.outerWidth()) + Number.parseInt($(this).outerWidth()) }).slideDown(120); } } else { submenu.slideUp(120); @@ -4308,6 +4315,7 @@ function setupEllipsis() { function keepWindowSize() { $(window).on('resize', function (event) { waitForFinalEvent(function () { + var graphTabWidth, userTabs, items, done; $('.cactiGraphContentArea').show(); var resizeTime = new Date(); @@ -4355,15 +4363,15 @@ function keepWindowSize() { $('.dropdownMenu > ul').hide(); if ($('#gtabs > .tabs').is(':visible')) { - var graphTabWidth = $('#gtabs > .tabs').outerWidth(); + graphTabWidth = $('#gtabs > .tabs').outerWidth(); } else { - var graphTabWidth = 0; + graphTabWidth = 0; } if ($('.usertabs').length) { - var userTabs = $('.usertabs').outerWidth(); + userTabs = $('.usertabs').outerWidth(); } else { - var userTabs = 0; + userTabs = 0; } var bodyWidth = $('body').width(); @@ -4372,7 +4380,7 @@ function keepWindowSize() { otherWidth += $(this).outerWidth(); }); - var ellipsisWidth = $('.maintabs-submenu-ellipsis').outerWidth(); + $('.maintabs-submenu-ellipsis').outerWidth(); var tabHeight = $('#tabs').outerHeight(); var mainTabPos = false; @@ -4390,8 +4398,8 @@ function keepWindowSize() { pageWidth = bodyWidth; - var items = $($('.maintabs nav ul li a.lefttab').get()); - var done = false; + items = $($('.maintabs nav ul li a.lefttab').get()); + done = false; items.each(function () { var id = $(this).attr('id'); @@ -4399,8 +4407,8 @@ function keepWindowSize() { }); // Hide top menus if you have to - var items = $($('.maintabs nav ul li a.lefttab').get().reverse()); - var done = false; + items = $($('.maintabs nav ul li a.lefttab').get().reverse()); + done = false; items.each(function () { var id = $(this).attr('id'); @@ -4447,15 +4455,16 @@ function showCurrentTab(id) { } function tabsWrapping() { + var mainTabHeight, userTabPos, gtabsWidth, utabsWidth; var mainTabPos = $('.maintabs:first').position(); var tabHeight = $('#tabs').height(); if ($('.usertabs').length) { - var mainTabHeight = tabHeight; - var userTabPos = $('.usertabs').position(); + mainTabHeight = tabHeight; + userTabPos = $('.usertabs').position(); } else { - var mainTabHeight = $('.maintabs:first nav').height(); - var userTabPos = mainTabPos; + mainTabHeight = $('.maintabs:first nav').height(); + userTabPos = mainTabPos; } var bodyWidth = $('body').width(); @@ -4468,15 +4477,15 @@ function tabsWrapping() { var mtabsWidth = $('.maintabs:not(.usertabs)').outerWidth(); if ($('#gtabs>.tabs').length) { - var gtabsWidth = $('#gtabs>.tabs').outerWidth(); + gtabsWidth = $('#gtabs>.tabs').outerWidth(); } else { - var gtabsWidth = 0; + gtabsWidth = 0; } if ($('.usertabs').length) { - var utabsWidth = $('.usertabs').outerWidth(); + utabsWidth = $('.usertabs').outerWidth(); } else { - var utabsWidth = 0; + utabsWidth = 0; } if (gtabsWidth + mtabsWidth + utabsWidth + otherWidth + ellipsisWidth > bodyWidth - 20) { @@ -4521,7 +4530,7 @@ function setCactiTabCookie() { } function checkForRedirects(data, href) { - if (typeof data == 'undefined') { + if (data === undefined) { return true; } else if (typeof data == 'object') { return true; @@ -4535,7 +4544,7 @@ function checkForRedirects(data, href) { $.ajaxQ.abortAll(); document.location = urlPath + 'permission_denied.php'; } else if (data.indexOf('cactiRedirect') >= 0) { - if (typeof href == 'undefined' || href == null) { + if (href === undefined || href == null) { $.ajaxQ.abortAll(); location.reload(); } else { @@ -4590,13 +4599,13 @@ function finalizeGraphFilter(options, data) { function applyGraphFilter() { var href = correctUrlParameters(graphPage + '?action=' + pageAction + '&rfilter=' + base64_encode($('#rfilter').val()) + - (typeof $('#site_id').val() != 'undefined' ? '&site_id=' + $('#site_id').val() : '') + - (typeof $('#location').val() != 'undefined' ? '&location=' + $('#location').val() : '') + - (typeof $('#host_id').val() != 'undefined' ? '&host_id=' + $('#host_id').val() : '') + - (typeof $('#graph_source').val() != 'undefined' ? '&graph_source=' + $('#graph_source').val() : '') + - (typeof $('#graph_order').val() != 'undefined' ? '&graph_order=' + $('#graph_order').val() : '') + - (typeof $('#cf').val() != 'undefined' ? '&cf=' + $('#cf').val() : '') + - (typeof $('#measure').val() != 'undefined' ? '&measure=' + $('#measure').val() : '') + + ($('#site_id').val() !== undefined ? '&site_id=' + $('#site_id').val() : '') + + ($('#location').val() !== undefined ? '&location=' + $('#location').val() : '') + + ($('#host_id').val() !== undefined ? '&host_id=' + $('#host_id').val() : '') + + ($('#graph_source').val() !== undefined ? '&graph_source=' + $('#graph_source').val() : '') + + ($('#graph_order').val() !== undefined ? '&graph_order=' + $('#graph_order').val() : '') + + ($('#cf').val() !== undefined ? '&cf=' + $('#cf').val() : '') + + ($('#measure').val() !== undefined ? '&measure=' + $('#measure').val() : '') + '&columns=' + $('#columns').val() + '&graphs=' + $('#graphs').val() + '&graph_template_id=' + $('#graph_template_id').val() + @@ -4618,7 +4627,7 @@ function pushState(myTitle, options) { options = sanitizeAjaxOptions(options); if (!options.noState) { if (statePushed == false) { - if (typeof window.history.pushState != 'undefined') { + if (window.history.pushState !== undefined) { var myObject = { Page: myTitle, Url: cleanUrlParameters(options.url), Options: Object.assign({}, options) }; window.history.pushState(myObject, myObject.Page, myObject.Url); } @@ -4630,11 +4639,11 @@ function pushState(myTitle, options) { function handlePopState(e) { var href = document.location.href; - if (typeof e !== 'undefined' && e.state != 'undefined' && e.state != null) { - if (typeof e.state.Options != 'undefined' && e.state.Options != null) { + if (e !== undefined && e.state != 'undefined' && e.state != null) { + if (e.state.Options !== undefined && e.state.Options != null) { loadUrl(e.state.Options); return false; - } else if (typeof e.state.Url != 'undefined' && e.state.Url != null) { + } else if (e.state.Url !== undefined && e.state.Url != null) { loadUrl({ url: href }); return false; } @@ -4647,13 +4656,13 @@ function applyGraphTimespan() { ($('#rfilter').length ? '&rfilter=' + base64_encode($('#rfilter').val()) : '') + '&predefined_timespan=' + $('#predefined_timespan').val() + '&predefined_timeshift=' + $('#predefined_timeshift').val() + - (typeof $('#site_id').val() != 'undefined' ? '&site_id=' + $('#site_id').val() : '') + - (typeof $('#location').val() != 'undefined' ? '&location=' + $('#location').val() : '') + - (typeof $('#host_id').val() != 'undefined' ? '&host_id=' + $('#host_id').val() : '') + - (typeof $('#graph_source').val() != 'undefined' ? '&graph_source=' + $('#graph_source').val() : '') + - (typeof $('#graph_order').val() != 'undefined' ? '&graph_order=' + $('#graph_order').val() : '') + - (typeof $('#cf').val() != 'undefined' ? '&cf=' + $('#cf').val() : '') + - (typeof $('#measure').val() != 'undefined' ? '&measure=' + $('#measure').val() : '') + + ($('#site_id').val() !== undefined ? '&site_id=' + $('#site_id').val() : '') + + ($('#location').val() !== undefined ? '&location=' + $('#location').val() : '') + + ($('#host_id').val() !== undefined ? '&host_id=' + $('#host_id').val() : '') + + ($('#graph_source').val() !== undefined ? '&graph_source=' + $('#graph_source').val() : '') + + ($('#graph_order').val() !== undefined ? '&graph_order=' + $('#graph_order').val() : '') + + ($('#cf').val() !== undefined ? '&cf=' + $('#cf').val() : '') + + ($('#measure').val() !== undefined ? '&measure=' + $('#measure').val() : '') + '&columns=' + $('#columns').val() + '&graphs=' + $('#graphs').val() + '&graph_template_id=' + $('#graph_template_id').val() + @@ -4795,15 +4804,15 @@ function redrawGraph(graph_id) { '&business_hours=' + (isBusiness ? 'true' : 'false') + $.getJSON(href, function(data) { - if (typeof data.status == 'undefined') { + if (data.status === undefined) { if (myWidth < data.image_width) { ratio = myWidth / data.image_width; - data.image_width = parseInt(data.image_width * ratio); - data.image_height = parseInt(data.image_height * ratio); - data.graph_width = parseInt(data.graph_width * ratio); - data.graph_height = parseInt(data.graph_height * ratio); - data.graph_top = parseInt(data.graph_top * ratio); - data.graph_left = parseInt(data.graph_left * ratio); + data.image_width = Number.parseInt(data.image_width * ratio); + data.image_height = Number.parseInt(data.image_height * ratio); + data.graph_width = Number.parseInt(data.graph_width * ratio); + data.graph_height = Number.parseInt(data.graph_height * ratio); + data.graph_top = Number.parseInt(data.graph_top * ratio); + data.graph_left = Number.parseInt(data.graph_left * ratio); } $('#wrapper_' + data.local_graph_id).empty().html( @@ -4916,7 +4925,7 @@ function initGraphDrilldown() { } function initializeGraphs(disable_cache) { - disable_cache = (typeof disable_cache == 'undefined') ? false : true; + disable_cache = (disable_cache === undefined) ? false : true; $.ajaxQ.abortAll(); @@ -5044,12 +5053,12 @@ function initializeGraphs(disable_cache) { if (myWidth < data.image_width) { ratio = myWidth / data.image_width; - data.image_width = parseInt(data.image_width * ratio); - data.image_height = parseInt(data.image_height * ratio); - data.graph_width = parseInt(data.graph_width * ratio); - data.graph_height = parseInt(data.graph_height * ratio); - data.graph_top = parseInt(data.graph_top * ratio); - data.graph_left = parseInt(data.graph_left * ratio); + data.image_width = Number.parseInt(data.image_width * ratio); + data.image_height = Number.parseInt(data.image_height * ratio); + data.graph_width = Number.parseInt(data.graph_width * ratio); + data.graph_height = Number.parseInt(data.graph_height * ratio); + data.graph_top = Number.parseInt(data.graph_top * ratio); + data.graph_left = Number.parseInt(data.graph_left * ratio); } var wrapper_id = '#wrapper_' + data.local_graph_id; @@ -5417,7 +5426,7 @@ $.widget('custom.dropcolor', { _select: function (event, ui) { var regExp = /\(([^)]+)\)/; var hex = regExp.exec(ui.item.label); - var id = $(ui.item.option).attr('value'); + $(ui.item.option).attr('value'); if (hex !== null) { color = hex[1]; @@ -5512,7 +5521,7 @@ function expandClipboardSection(section) { section.slideDown('fast'); } - var children = section.find('table').each(function (i) { + section.find('table').each(function (i) { expandClipboardSection($(this)); }); } @@ -5821,16 +5830,17 @@ function setSNMP() { } function checkSNMPPassphrase(type) { + var pass, conf, span; var minChars = 8; if (type == 'priv') { - var pass = '#snmp_priv_passphrase'; - var conf = '#snmp_priv_passphrase_confirm'; - var span = 'priv'; + pass = '#snmp_priv_passphrase'; + conf = '#snmp_priv_passphrase_confirm'; + span = 'priv'; } else { - var pass = '#snmp_password'; - var conf = '#snmp_password_confirm'; - var span = 'auth'; + pass = '#snmp_password'; + conf = '#snmp_password_confirm'; + span = 'auth'; } if ($(pass).val().length == 0) { @@ -5848,18 +5858,19 @@ function checkSNMPPassphrase(type) { } function checkSNMPPassphraseConfirm(type) { + var pass, conf, span, spanconf; var minChars = 8; if (type == 'priv') { - var pass = '#snmp_priv_passphrase'; - var conf = '#snmp_priv_passphrase_confirm'; - var span = 'priv'; - var spanconf = 'privconf'; + pass = '#snmp_priv_passphrase'; + conf = '#snmp_priv_passphrase_confirm'; + span = 'priv'; + spanconf = 'privconf'; } else { - var pass = '#snmp_password'; - var conf = '#snmp_password_confirm'; - var span = 'auth'; - var spanconf = 'authconf'; + pass = '#snmp_password'; + conf = '#snmp_password_confirm'; + span = 'auth'; + spanconf = 'authconf'; } if ($(conf).val().length < minChars) { @@ -5897,7 +5908,7 @@ const streamAvailabilityToggleOptions = ['0', '1', '2', '3', '4', '5', '6']; const streamSNMPToggleOptions = ['1', '2', '3']; function setAvailability() { - var hasChanged = false; + if ($('#availability_method').val() != '7') { if ($('#snmp_version').val() == '0') { @@ -6020,7 +6031,7 @@ function formValidate(formId, href) { } if (name !== null && name.length > 0) { - if (typeof formRules[formId] !== 'undefined') { + if (formRules[formId] !== undefined) { formRules[formId][name] = { required: true, email: true, @@ -6080,7 +6091,7 @@ function toggleFields(fields, prefix = '#row_') { Object.keys(fields).forEach(index => { curParent = $(prefix + index).parent().attr('id'); - if (typeof curParent != 'undefined' && curParent != lastParent) { + if (curParent !== undefined && curParent != lastParent) { parents.push(curParent); } @@ -6092,7 +6103,7 @@ function toggleFields(fields, prefix = '#row_') { $(prefix + index).hide(); } - if (typeof curParent != 'undefined') { + if (curParent !== undefined) { lastParent = curParent; } }); diff --git a/include/realtime.js b/include/realtime.js index 0bc4bde67..e11b5fe46 100644 --- a/include/realtime.js +++ b/include/realtime.js @@ -38,16 +38,17 @@ var url; var local_graph_id = null; function realtimeDetectBrowser() { + var browser; if (navigator.userAgent.indexOf('MSIE') >= 0) { - var browser = 'IE'; + browser = 'IE'; } else if (navigator.userAgent.indexOf('Chrome') >= 0) { - var browser = 'Chrome'; + browser = 'Chrome'; } else if (navigator.userAgent.indexOf('Mozilla') >= 0) { - var browser = 'FF'; + browser = 'FF'; } else if (navigator.userAgent.indexOf('Opera') >= 0) { - var browser = 'Opera'; + browser = 'Opera'; } else { - var browser = 'Other'; + browser = 'Other'; } return browser; @@ -55,7 +56,7 @@ function realtimeDetectBrowser() { function imageOptionsChanged(action) { const graph_start = $('#graph_start').val(); - var graph_end = 0; + const ds_step = $('#ds_step').val(); var size = $('#size').val(); const isThumb = $('#thumbnails').is(':checked'); @@ -78,12 +79,12 @@ function imageOptionsChanged(action) { if (action === 'countdown') { url = `graph_realtime.php?action=countdown&top=0&left=0&local_graph_id=${local_graph_id}&ds_step=${ds_step}&count=${count}&size=${size}&graph_nolegend=${isThumb}`; } else if (action === 'initial') { - url = `graph_realtime.php?action=initial&top=0&left=0&local_graph_id=${local_graph_id}&graph_start=-${parseInt(graph_start) > 0 ? graph_start:'60'}&ds_step=${ds_step}&count=${count}&size=${size}`; + url = `graph_realtime.php?action=initial&top=0&left=0&local_graph_id=${local_graph_id}&graph_start=-${Number.parseInt(graph_start) > 0 ? graph_start:'60'}&ds_step=${ds_step}&count=${count}&size=${size}`; } else { - url = `graph_realtime.php?action=${action}&top=0&left=0&local_graph_id=${local_graph_id}&graph_start=-${parseInt(graph_start) > 0 ? graph_start:'60'}&ds_step=${ds_step}&count=${count}&size=${size}&graph_nolegend=${isThumb}`; + url = `graph_realtime.php?action=${action}&top=0&left=0&local_graph_id=${local_graph_id}&graph_start=-${Number.parseInt(graph_start) > 0 ? graph_start:'60'}&ds_step=${ds_step}&count=${count}&size=${size}&graph_nolegend=${isThumb}`; } - Pace.stop; + Pace.stop(); $.getJSON(url) .done(function(data) { @@ -170,7 +171,7 @@ function countRealtimeGraphs() { } function stopRealtime() { - var graph; + for (key in realtimeArray) { var graph_id = key; @@ -239,7 +240,7 @@ function realtimeGrapher() { } const graph_start = $('#graph_start').val(); - var graph_end = 0; + const ds_step = $('#ds_step').val(); var size = $('#size').val(); const isThumb = $('#thumbnails').is(':checked'); @@ -276,7 +277,7 @@ function realtimeGrapher() { position = $('body').position(); } - $.get(`${urlPath}graph_realtime.php?action=countdown&top=${parseInt(position.top)}&left=${parseInt(position.left)}${isThumb ? '&graph_nolegend=true':'&graph_nolegend=false'}&graph_end=0&graph_start=-${parseInt(graph_start) > 0 ? graph_start:'60'}&local_graph_id=${local_graph_id}&ds_step=${ds_step}&count=${count}&size=${size}`) + $.get(`${urlPath}graph_realtime.php?action=countdown&top=${Number.parseInt(position.top)}&left=${Number.parseInt(position.left)}${isThumb ? '&graph_nolegend=true':'&graph_nolegend=false'}&graph_end=0&graph_start=-${Number.parseInt(graph_start) > 0 ? graph_start:'60'}&local_graph_id=${local_graph_id}&ds_step=${ds_step}&count=${count}&size=${size}`) .done(function(data) { var results = (typeof data === 'string') ? JSON.parse(data) : data; diff --git a/include/themes/cacti/main.js b/include/themes/cacti/main.js index cd3da52b7..16abaf657 100644 --- a/include/themes/cacti/main.js +++ b/include/themes/cacti/main.js @@ -134,12 +134,12 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings $('#navigation').hide(); $('li.menuitem').each(function() { - active = storage.get($(this).attr('id')); + const active = storage.get($(this).attr('id')); if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); $(this).next('a').show(); @@ -155,7 +155,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')){ $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -172,8 +172,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/carrot/main.js b/include/themes/carrot/main.js index cd3da52b7..16abaf657 100644 --- a/include/themes/carrot/main.js +++ b/include/themes/carrot/main.js @@ -134,12 +134,12 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings $('#navigation').hide(); $('li.menuitem').each(function() { - active = storage.get($(this).attr('id')); + const active = storage.get($(this).attr('id')); if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); $(this).next('a').show(); @@ -155,7 +155,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')){ $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -172,8 +172,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/dark/main.js b/include/themes/dark/main.js index d010b93ad..de2d11dc5 100644 --- a/include/themes/dark/main.js +++ b/include/themes/dark/main.js @@ -96,18 +96,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -132,7 +128,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -149,8 +145,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/deepness/main.js b/include/themes/deepness/main.js index 1fc26dd5b..01e1d5fd5 100644 --- a/include/themes/deepness/main.js +++ b/include/themes/deepness/main.js @@ -135,18 +135,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -172,7 +168,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -189,8 +185,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/hollyberry/main.js b/include/themes/hollyberry/main.js index cd3da52b7..16abaf657 100644 --- a/include/themes/hollyberry/main.js +++ b/include/themes/hollyberry/main.js @@ -134,12 +134,12 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings $('#navigation').hide(); $('li.menuitem').each(function() { - active = storage.get($(this).attr('id')); + const active = storage.get($(this).attr('id')); if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); $(this).next('a').show(); @@ -155,7 +155,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')){ $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -172,8 +172,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/midwinter/main.js b/include/themes/midwinter/main.js index 59952ba1c..a1e898c30 100755 --- a/include/themes/midwinter/main.js +++ b/include/themes/midwinter/main.js @@ -456,7 +456,7 @@ function setupTheme() { }); } else { $("#navBackdrop").on('click', function() { - window.open('https://cacti.net', '_blank'); + window.open('https://cacti.net', '_blank', 'noopener,noreferrer'); }); } } @@ -597,7 +597,7 @@ function toggleCactiNavigationBoxPin(event) { let caller = $(event.currentTarget); let helper = caller.attr('data-helper'); let navigationBox = $('[class^="mdw-ConsoleNavigationBox"][data-helper="' + helper + '"]'); - let compact_nav_icon = $('[class^="compact_nav_icon"][data-helper="' + helper + '"]'); + $('[class^="compact_nav_icon"][data-helper="' + helper + '"]'); if(event.data && event.data.dock) { event.data.dock = event.data.dock.replace(/^./, str => str.toUpperCase()); @@ -670,7 +670,7 @@ function hideDropDownMenu() { function toggleTableColumn(event) { let storage = Storages.localStorage; let tableHash = event.target.dataset.table; - let cIndex = parseInt(event.target.dataset.column); + let cIndex = Number.parseInt(event.target.dataset.column); let cClass = 'no-col'+cIndex; let storage_table_headers = storage.get('midWinter_' + tableHash); @@ -914,7 +914,7 @@ function changeGuiFontSize(change=true) { setDocumentAttribute('zoom-level', mdw.session.theme.font.zoom); } /* update output field beside input selector */ - $('#mdw_themeFontSizeValue').val((parseFloat(mdw.session.theme.font.zoom) + 25).toFixed(1) + ' %'); + $('#mdw_themeFontSizeValue').val((Number.parseFloat(mdw.session.theme.font.zoom) + 25).toFixed(1) + ' %'); } function toggleGuiAnimations() { @@ -1022,9 +1022,9 @@ function togglePopOver(force) { function fullScreen(event) { if (!document.fullscreenElement) { - document.documentElement.requestFullscreen().then( r => fullScreenChangeHandler() ); + document.documentElement.requestFullscreen().then(() => fullScreenChangeHandler()).catch(() => fullScreenChangeHandler()); }else if (document.exitFullscreen) { - document.exitFullscreen().then( r => fullScreenChangeHandler() ); + document.exitFullscreen().then(() => fullScreenChangeHandler()).catch(() => fullScreenChangeHandler()); } } @@ -1328,7 +1328,7 @@ registry.midwinter = { let midWinter_Color_Mode_Auto = mdw.session.theme.color.auto; let midWinter_Font_Size = mdw.session.theme.font.zoom; let midWinter_Animations = mdw.session.theme.boxes.animated - let midWinter_ShownFontSizeValue = parseFloat(midWinter_Font_Size) + 25; + let midWinter_ShownFontSizeValue = Number.parseFloat(midWinter_Font_Size) + 25; let midWinter_Auto_Table_Layout = mdw.session.theme.mobile.autoTableLayout; let midWinter_Controls_SubTitle = mdw.session.theme.controls.subTitle; @@ -1413,4 +1413,4 @@ registry.midwinter = { } } } -} \ No newline at end of file +} diff --git a/include/themes/modern/main.js b/include/themes/modern/main.js index d08b69797..faa53cadf 100644 --- a/include/themes/modern/main.js +++ b/include/themes/modern/main.js @@ -88,18 +88,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -125,7 +121,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -142,8 +138,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/paper-plane/main.js b/include/themes/paper-plane/main.js index a286eec5e..bdf4f160e 100644 --- a/include/themes/paper-plane/main.js +++ b/include/themes/paper-plane/main.js @@ -153,18 +153,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -190,7 +186,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -207,8 +203,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/paw/main.js b/include/themes/paw/main.js index ebacbbce6..c1c9c6ed2 100644 --- a/include/themes/paw/main.js +++ b/include/themes/paw/main.js @@ -135,18 +135,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -172,7 +168,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -189,8 +185,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/raspberry/main.js b/include/themes/raspberry/main.js index cd3da52b7..16abaf657 100644 --- a/include/themes/raspberry/main.js +++ b/include/themes/raspberry/main.js @@ -134,12 +134,12 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings $('#navigation').hide(); $('li.menuitem').each(function() { - active = storage.get($(this).attr('id')); + const active = storage.get($(this).attr('id')); if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); $(this).next('a').show(); @@ -155,7 +155,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')){ $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -172,8 +172,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/include/themes/sunrise/main.js b/include/themes/sunrise/main.js index 5b87a0e4c..c2417e116 100644 --- a/include/themes/sunrise/main.js +++ b/include/themes/sunrise/main.js @@ -28,7 +28,7 @@ function themeReady() { $('#navigation_right').rebind('scroll', function (event) { var scroll_position_x = $('#navigation_right').scrollLeft(); var scroll_position_y = $('#navigation_right').scrollTop(); - $('.bottom_scroll_up').css({'color': ((scroll_position_x == 0 & scroll_position_y == 0) ? '' : '#93CEFF') }); + $('.bottom_scroll_up').css({'color': ((scroll_position_x === 0 && scroll_position_y === 0) ? '' : '#93CEFF') }); }); // Add nice search filter to filters @@ -158,18 +158,14 @@ function themeReady() { } function setMenuVisibility() { - storage=Storages.sessionStorage; + const storage = Storages.sessionStorage; // Initialize the navigation settings // This will setup the initial visibility of the menu $('li.menuitem').each(function() { - var id = $(this).attr('id'); + const id = $(this).attr('id'); - if (storage.isSet(id)) { - var active = storage.get(id); - } else { - var active = null; - } + const active = storage.isSet(id) ? storage.get(id) : null; if (active != null && active == 'active') { $(this).find('ul').attr('aria-hidden', 'false').attr('aria-expanded', 'true').show(); @@ -195,7 +191,7 @@ function setMenuVisibility() { $('#nav li:has(ul) a.active').rebind('click', function(event) { event.preventDefault(); - id = $(this).closest('.menuitem').attr('id'); + const id = $(this).closest('.menuitem').attr('id'); if ($(this).next().is(':visible')) { $(this).next('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); @@ -212,8 +208,6 @@ function setMenuVisibility() { } $('li.menuitem').not('#'+id).each(function() { - text = $(this).attr('id'); - id = $(this).attr('id'); $(this).find('ul').attr('aria-hidden', 'true').attr('aria-expanded', 'false'); $(this).find('ul').slideUp( { duration: 200, easing: 'swing' } ); diff --git a/install/install.js b/install/install.js index 2949918ca..09146d837 100644 --- a/install/install.js +++ b/install/install.js @@ -127,7 +127,7 @@ function setButtonData(buttonName, buttonData) { button.button(); button.data('buttonData', buttonData); if (buttonData != null) { - var buttonCheck = button.data('buttonData'); + button.data('buttonData'); if (buttonData.Enabled) { button.button('enable'); } else { @@ -231,7 +231,7 @@ function getDefaultInstallData() { } function toggleHeader(key, initial) { - if (typeof initial === 'undefined') { + if (initial === undefined) { initial = null; } @@ -254,7 +254,7 @@ function toggleHeader(key, initial) { } function toggleSection(key, initial) { - if (typeof initial === 'undefined') { + if (initial === undefined) { initial = null; } @@ -559,7 +559,7 @@ function prepareInstallData(installStep, stepOnly) { } // No installation step if we have never started. - if (typeof installStep === 'undefined' && installData.Step == STEP_NONE) { + if (installStep === undefined && installData.Step == STEP_NONE) { newData = []; } else { newData = getDefaultInstallData(); @@ -572,12 +572,12 @@ function prepareInstallData(installStep, stepOnly) { } } - if (typeof installStep !== 'undefined') { + if (installStep !== undefined) { step = installData.Step; if (step == STEP_WELCOME) prepareStepWelcome(newData); // Assume we want all data if stepOnly not set - if (typeof stepOnly === 'undefined') { + if (stepOnly === undefined) { if (step == STEP_INSTALL_TYPE) prepareStepInstallType(newData); else if (step == STEP_BINARY_LOCATIONS) prepareStepBinaryLocations(newData); else if (step == STEP_PROFILE_AND_AUTOMATION) prepareStepProfileAndAutomation(newData); @@ -700,7 +700,7 @@ function performStep(installStep, suppressRefresh, forceReload, csrfRetry) { $('#installContent').html(data.Html); $('#installContent').show(); - if (typeof $('#installData').data('debug') !== 'undefined') { + if ($('#installData').data('debug') !== undefined) { debugData = data; debugData.Html = ''; debug = $('#installDebug'); @@ -822,7 +822,7 @@ function performTestConnection(csrfRetry) { $('#installContent').removeClass('cactiInstallLoaderBlur'); var isSuccessful = false, statusText = testFailed; - if (typeof data.status !== 'undefined') { + if (data.status !== undefined) { if (data.status == 'true') { isSuccessful = true; statusText = testSuccessful; diff --git a/lib/html.php b/lib/html.php index 12984d7b3..13bf446e4 100644 --- a/lib/html.php +++ b/lib/html.php @@ -3200,7 +3200,8 @@ function html_common_header(string $title, string $selectedTheme = '') : void { var showHideFilter = '<?php print __esc('Click to Show/Hide Filter'); ?>'; var spikeKillResults = '<?php print __esc('SpikeKill Results'); ?>'; var standardGraphicalUserInterface = '<?php print __esc('Standard Mode'); ?>'; - var tableConstraints = '<?php print __esc('Allow or limit the table columns to extend beyond the current windows limits.'); ?>'; + var tableConstraints = <?php print cacti_js_encode(__esc('Allow or limit the table columns to extend beyond the current windows ' . + 'limits.')); ?>; var testFailed = '<?php print __esc('Connection Failed'); ?>'; var testSuccessful = '<?php print __esc('Connection Successful'); ?>'; var theme = '<?php print $selectedTheme; ?>'; @@ -3245,7 +3246,8 @@ function html_common_header(string $title, string $selectedTheme = '') : void { var zoom_i18n_zoom_out_factor = '<?php print __esc('Zoom Out Factor'); ?>'; var zoom_i18n_zoom_out_positioning = '<?php print __esc('Zoom Out Positioning'); ?>'; var zoom_outOfRangeTitle='<?php print __esc('Zoom Window Out of Range'); ?>'; - var zoom_outOfRangeMessage='<?php print __esc('Zoom Dates before January 1, 1993 are not supported in Cacti! Pick a more recent date.'); ?>'; + var zoom_outOfRangeMessage= <?php print cacti_js_encode(__esc('Zoom Dates before January 1, 1993 are not supported in Cacti! Pick a ' . + 'more recent date.')); ?>; </script> <?php // Global icons diff --git a/lib/ldap.php b/lib/ldap.php index 906de6acd..429d785de 100644 --- a/lib/ldap.php +++ b/lib/ldap.php @@ -477,7 +477,7 @@ class Ldap { public string $cn_full_name; public string $cn_email; - function __construct() { + public function __construct() { // No DB lookup here: Cacti\Auth\LdapLoginProvider::buildLdap() is the // single source of truth for mapping login_providers.parameters onto // these properties, so this class stays a pure connection/protocol @@ -486,14 +486,14 @@ class Ldap { $this->host = ''; } - function __destruct() { + public function __destruct() { } - function ErrorHandler(int $level, string $message, string $file, int $line, array $context = []) : bool { + public function ErrorHandler(int $level, string $message, string $file, int $line, array $context = []) : bool { return true; } - function SetLdapHandler() : void { + public function SetLdapHandler() : void { // drop out of cactis error handler restore_error_handler(); @@ -503,7 +503,7 @@ class Ldap { cacti_session_close(); } - function RestoreCactiHandler() : void { + public function RestoreCactiHandler() : void { // drop out of ldaps error handler restore_error_handler(); @@ -513,13 +513,13 @@ class Ldap { cacti_session_start(); } - function RecordError(array $output, string $section = 'LDAP') : void { + public function RecordError(array $output, string $section = 'LDAP') : void { $logDN = empty($output['dn']) ? '' : (', DN: ' . $output['dn']); cacti_log($section . ': ' . $output['error_text'] . $logDN, false, 'AUTH'); cacti_log($section . ': ' . $output['stack'], false, 'AUTH', $this->debug); } - function Connect() : array { + public function Connect() : array { $output = []; $ldap_conn = null; @@ -698,7 +698,7 @@ class Ldap { } } - function Authenticate() : array { + public function Authenticate() : array { $output = []; cacti_log('LDAP: Authentication Start', false, 'AUTH', $this->debug); @@ -838,7 +838,7 @@ class Ldap { return $output; } - function GetMask() : int { + public function GetMask() : int { if (!defined('ENT_HTML401')) { return ENT_COMPAT; } else { @@ -869,7 +869,7 @@ class Ldap { * failure) so the caller can leave assignments untouched * rather than revoking them on a transient outage. */ - function ResolveGroupMemberships(string $username, array $groupNames) : ?array { + public function ResolveGroupMemberships(string $username, array $groupNames) : ?array { if (!cacti_sizeof($groupNames)) { return []; } @@ -1136,7 +1136,7 @@ class Ldap { return $queries; } - function Search() : array { + public function Search() : array { $output = []; // Determine connection method and create LDAP Object @@ -1256,7 +1256,7 @@ class Ldap { return $output; } - function Getcn() : array { + public function Getcn() : array { $output = []; // Determine connection method and create LDAP Object @@ -1397,7 +1397,7 @@ class Ldap { return $output; } - function isUserInLDAPGroup(object $ldapConn, string $ldapbasedn, string $groupDN, string $ldapUser) : bool { + public function isUserInLDAPGroup(object $ldapConn, string $ldapbasedn, string $groupDN, string $ldapUser) : bool { $query = cacti_ldap_filter( '(&(distinguishedName=<user>)(memberOf:1.2.840.113556.1.4.1941:=<group>))', ['user' => $ldapUser, 'group' => $groupDN] diff --git a/lib/mib_parser.php b/lib/mib_parser.php index 27a2f032d..0a45f837b 100644 --- a/lib/mib_parser.php +++ b/lib/mib_parser.php @@ -56,13 +56,13 @@ class MibParser extends MibCache { /** * Constructor */ - function __construct() { + public function __construct() { set_time_limit(0); ini_set('memory_limit', '-1'); error_reporting(E_ALL); } - function add_mib(string $filename, string $mib_name): void { + public function add_mib(string $filename, string $mib_name): void { $mibtext = file_get_contents($filename); if ($mibtext === false) { @@ -78,7 +78,7 @@ class MibParser extends MibCache { * @param string $text * @return array */ - function get_tokens($text) { + public function get_tokens($text) { $in_quote = false; $in_comment = false; $token = ''; @@ -197,7 +197,7 @@ class MibParser extends MibCache { * @param array $allowed * @return array */ - function parse_simple_token($tokens, &$index, $allowed = null) { + public function parse_simple_token($tokens, &$index, $allowed = null) { $index++; if (is_array($allowed)) { @@ -222,7 +222,7 @@ class MibParser extends MibCache { * @param integer $index * @return array */ - function parse_SYNTAX_token($tokens, &$index) { + public function parse_SYNTAX_token($tokens, &$index) { $ret = null; switch($tokens[$index + 1]) { @@ -316,7 +316,7 @@ class MibParser extends MibCache { * @param string $end * @return array */ - function parse_bracket_token($tokens, &$index, $start, $end) { + public function parse_bracket_token($tokens, &$index, $start, $end) { $begin = $index + 1; while ($index + 1 < count($tokens) && $tokens[$index] != $end) { @@ -338,7 +338,7 @@ class MibParser extends MibCache { * @param boolean $full * @param mixed $mib_name */ - function parse_mib($mibtext, $mib_name, $full = false): void { + public function parse_mib($mibtext, $mib_name, $full = false): void { $tokens = MibParser::get_tokens($mibtext); $cnt = count($tokens); $rec = []; @@ -424,7 +424,7 @@ class MibParser extends MibCache { } } - function generate(): void { + public function generate(): void { $this->oids['enterprises'] = ['oid' => '.1.3.6.1.4.1']; foreach ($this->parsed as $object) { diff --git a/lib/ping.php b/lib/ping.php index 645f7abe5..b4e4fac7a 100644 --- a/lib/ping.php +++ b/lib/ping.php @@ -42,23 +42,23 @@ class Net_Ping { public int $avail_method; public int $ping_type; - function __construct() { + public function __construct() { $this->port = 33439; } - function __destruct() { + public function __destruct() { } - function close_socket() : void { + public function close_socket() : void { @socket_shutdown($this->socket, 2); socket_close($this->socket); } - function start_time() : void { + public function start_time() : void { $this->timer_start_time = microtime(true); } - function get_time(int $acc = 2) : mixed { + public function get_time(int $acc = 2) : mixed { // format start time $start_time = $this->timer_start_time; // get and format end time @@ -67,7 +67,7 @@ class Net_Ping { return number_format($end_time - $start_time, $acc); } - function build_udp_packet() : void { + public function build_udp_packet() : void { $data = 'cacti-monitoring-system'; // the actual test data // now lets build the actual UDP packet @@ -75,19 +75,19 @@ class Net_Ping { $this->request_len = strlen($this->request); } - function ping_error_handler(int $errno, string $errmsg, string $filename, int $linenum, array $vars = []) : bool { + public function ping_error_handler(int $errno, string $errmsg, string $filename, int $linenum, array $vars = []) : bool { return true; } - function set_ping_error_handler() : void { + public function set_ping_error_handler() : void { set_error_handler([$this, 'ping_error_handler']); } - function restore_cacti_error_handler() : void { + public function restore_cacti_error_handler() : void { restore_error_handler(); } - function build_icmp_packet() : void { + public function build_icmp_packet() : void { $seq_low = random_int(0,255); $seq_high = random_int(0,255); @@ -107,7 +107,7 @@ class Net_Ping { $this->request_len = strlen($this->request); } - function get_checksum(string $data) : string { + public function get_checksum(string $data) : string { if (strlen($data) % 2) { $data .= "\x00"; } @@ -122,7 +122,7 @@ class Net_Ping { return pack('n*', ~$sum); } - function ping_icmp() : bool { + public function ping_icmp() : bool { // ping me if ($this->host['hostname']) { // initialize variables @@ -299,7 +299,7 @@ class Net_Ping { } } - function seteuid() : int { + public function seteuid() : int { $cacti_user = ''; // if we are unix, set the effective userid to root and then create @@ -312,7 +312,7 @@ class Net_Ping { return $cacti_user; } - function setuid(int $cacti_poller_account) : void { + public function setuid(int $cacti_poller_account) : void { // if we are unix, set the effective userid to root and then create if ((CACTI_SERVER_OS == 'unix') && (function_exists('posix_getuid'))) { @@ -320,7 +320,7 @@ class Net_Ping { } } - function ping_snmp() : mixed { + public function ping_snmp() : mixed { // initialize variables $this->snmp_status = 'down'; $this->snmp_response = 'Device did not respond to SNMP'; @@ -352,7 +352,7 @@ class Net_Ping { return $result; } - function get_snmp_result(string $oid) : bool { + public function get_snmp_result(string $oid) : bool { $h = $this->host; // getnext does not work in php versions less than 5 @@ -389,7 +389,7 @@ class Net_Ping { } } // ping_snmp - function ping_udp() : bool { + public function ping_udp() : bool { $this->set_ping_error_handler(); // hostname must be nonblank @@ -519,7 +519,7 @@ class Net_Ping { } } // end ping_udp - function ping_tcp() : bool { + public function ping_tcp() : bool { $this->set_ping_error_handler(); // hostname must be nonblank @@ -651,7 +651,7 @@ class Net_Ping { } } // end ping_tcp - function ping(int $avail_method = AVAIL_SNMP_AND_PING, int $ping_type = PING_ICMP, int $timeout = 500, int $retries = 3) : bool { + public function ping(int $avail_method = AVAIL_SNMP_AND_PING, int $ping_type = PING_ICMP, int $timeout = 500, int $retries = 3) : bool { $this->set_ping_error_handler(); // initialize variables @@ -761,7 +761,7 @@ class Net_Ping { }; } // end_ping - function is_ipaddress(string $ip_address = '') : bool { + public function is_ipaddress(string $ip_address = '') : bool { // check for ipv4/v6 if (function_exists('filter_var')) { if (filter_var($ip_address, FILTER_VALIDATE_IP) !== false) { @@ -776,7 +776,7 @@ class Net_Ping { } } - function strip_ip_address(string $ip_address) : string { + public function strip_ip_address(string $ip_address) : string { // clean up hostname if specifying snmp_transport if (str_contains($ip_address, 'tcp6:')) { $ip_address = str_replace('tcp6:', '', cacti_strtolower($ip_address)); diff --git a/settings.php b/settings.php index 3e16aa5e5..b58d68a88 100644 --- a/settings.php +++ b/settings.php @@ -262,8 +262,13 @@ function display_settings() : void { var currentTab = '<?php print $current_tab; ?>'; var dataCollectors = '<?php print $data_collectors; ?>'; var permsTitle = '<?php print __esc('Changing Permission Model Warning'); ?>'; - var permsHeader = '<?php print __esc('Changing Permission Model will alter a users effective Graph permissions.'); ?>'; - var permsMessage = '<?php print __esc('After you change the Graph Permission Model you should audit your Users and User Groups Effective Graph permission to ensure that you still have adequate control of your Graphs. NOTE: If you want to restrict all Graphs at the Device or Graph Template Graph Permission Model, the default Graph Policy should be set to \'Deny\'.'); ?>'; + var permsHeader = <?php print cacti_js_encode(__esc('Changing Permission Model will alter a users effective Graph ' . + 'permissions.')); ?>; + var permsMessage = <?php print cacti_js_encode(__esc('After you change the Graph Permission Model you should audit your ' . + 'Users and User Groups Effective Graph permission to ensure that you ' . + 'still have adequate control of your Graphs. NOTE: If you want to ' . + 'restrict all Graphs at the Device or Graph Template Graph Permission ' . + 'Model, the default Graph Policy should be set to \'Deny\'.')); ?>; $(function() { $('#filter').keyup(function() { diff --git a/tests/Unit/Core/PublicMethodVisibilityTest.php b/tests/Unit/Core/PublicMethodVisibilityTest.php new file mode 100644 index 000000000..b9f435718 --- /dev/null +++ b/tests/Unit/Core/PublicMethodVisibilityTest.php @@ -0,0 +1,59 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | Copyright (C) 2004-2026 The Cacti Group | + | Licensed under the GNU General Public License, version 2 or later. | + +-------------------------------------------------------------------------+ + | Cacti: The Complete RRDtool-based Graphing Solution | + +-------------------------------------------------------------------------+ +*/ + +function cacti_test_public_method_contracts(): array { + static $contracts = null; + + if ($contracts !== null) { + return $contracts; + } + + // Load the production classes in a separate process. Collecting this test + // must not alter class/function ownership in other Cacti unit test files. + $probe = dirname(__DIR__, 2) . '/fixtures/Core/PublicMethodContractProbe.php'; + $process = proc_open([PHP_BINARY, $probe], [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], $pipes); + + if (!is_resource($process)) { + throw new RuntimeException('Unable to start the public API probe.'); + } + + fclose($pipes[0]); + $output = stream_get_contents($pipes[1]); + $errors = stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + + if (proc_close($process) !== 0) { + throw new RuntimeException($errors); + } + + $contracts = json_decode($output, true, 512, JSON_THROW_ON_ERROR); + + return $contracts; +} + +$baseline = json_decode(file_get_contents( + dirname(__DIR__, 2) . '/fixtures/Core/public-method-signatures.json' +), true, 512, JSON_THROW_ON_ERROR); +$cases = []; + +foreach ($baseline as $name => $contract) { + $cases[$name] = [$name, $contract]; +} + +test('the public method retains its complete Cacti develop call contract', function (string $name, array $expected) { + $actual = cacti_test_public_method_contracts(); + expect($actual)->toHaveCount(46); + expect($actual[$name])->toBe($expected); +})->with($cases); diff --git a/tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php b/tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php index 0ae8d4929..fb01d164f 100644 --- a/tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php +++ b/tests/Unit/Security/Auth/LoginProviderSecretFieldsTest.php @@ -127,7 +127,9 @@ test('form_cert_box() shows the expiration date of a pasted valid certificate', form_cert_box('idp_x509cert', $pem, '', 6, 60); $html = ob_get_clean(); - $expected = date('Y-m-d', strtotime('+30 days')); + $certificate = openssl_x509_parse($pem); + expect($certificate)->not->toBeFalse(); + $expected = date('Y-m-d', $certificate['validTo_time_t']); expect($html)->toContain('<textarea'); expect($html)->toContain('Good Till: ' . $expected); diff --git a/tests/Unit/Security/Xss/InlineTranslationJsonTest.php b/tests/Unit/Security/Xss/InlineTranslationJsonTest.php new file mode 100644 index 000000000..faec9b97f --- /dev/null +++ b/tests/Unit/Security/Xss/InlineTranslationJsonTest.php @@ -0,0 +1,41 @@ +<?php +// Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later. + +require_once dirname(__DIR__, 3) . '/Helpers/CactiStubs.php'; +require_once CACTI_PATH_INCLUDE . '/global.php'; + +test('inline translated values use the shared JSON boundary and preserve HTML escaping', function (string $path, string $variable, string $catalogMessage) { + $source = file_get_contents(CACTI_PATH_BASE . '/' . $path); + $pattern = '/\bvar\s+' . preg_quote($variable, '/') . '\s*=\s*[\'\"]?<\?php\s+print\s+(.*?)\?>/s'; + expect(preg_match($pattern, $source, $match))->toBe(1); + $parser = (new \PhpParser\ParserFactory())->createForNewestSupportedVersion(); + $statements = $parser->parse('<?php print ' . trim($match[1])); + $expression = $statements[0]->expr; + expect($expression)->toBeInstanceOf(\PhpParser\Node\Expr\Print_::class); + $encodedCall = $expression->expr; + expect($encodedCall)->toBeInstanceOf(\PhpParser\Node\Expr\FuncCall::class); + expect($encodedCall->name->toString())->toBe('cacti_js_encode'); + $translatedCall = $encodedCall->args[0]->value; + expect($translatedCall)->toBeInstanceOf(\PhpParser\Node\Expr\FuncCall::class); + expect($translatedCall->name->toString())->toBe('__esc'); + + // Supply adversarial catalog text to the actual production expression; + // neither the translation helper nor the JSON encoder is replaced. + $translatedCall->args[0]->value = new \PhpParser\Node\Expr\Variable('catalogMessage'); + $printer = new \PhpParser\PrettyPrinter\Standard(); + $encoded = eval('return ' . $printer->prettyPrintExpr($encodedCall) . ';'); + expect(json_decode($encoded, true, 512, JSON_THROW_ON_ERROR))->toBe(__esc($catalogMessage)); + expect($encoded)->not->toContain('</script>'); + expect($encoded)->not->toContain("\u{2028}"); + expect($encoded)->not->toContain("\u{2029}"); +})->with([ + ['settings.php', 'permsHeader'], + ['settings.php', 'permsMessage'], + ['lib/html.php', 'tableConstraints'], + ['lib/html.php', 'zoom_outOfRangeMessage'], +])->with([ + 'quotes and backslashes' => ["single' double\" backslash\\"], + 'script closing text' => ['</script><script>window.untrusted=true</script>'], + 'line terminators' => ["first\nsecond\u{2028}third\u{2029}fourth"], + 'HTML entities' => ['" ' <b>caption</b>'], +]); diff --git a/tests/e2e/tests/develop-client-security.spec.ts b/tests/e2e/tests/develop-client-security.spec.ts new file mode 100644 index 000000000..e6b012b85 --- /dev/null +++ b/tests/e2e/tests/develop-client-security.spec.ts @@ -0,0 +1,27 @@ +/* Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later. */ +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +test('Midwinter homepage popup cannot access its opener', async ({ page, context }) => { + const source = readFileSync(resolve(__dirname, '../../../include/themes/midwinter/main.js'), 'utf8'); + const sink = source.indexOf("window.open('https://cacti.net'"); + expect(sink).toBeGreaterThan(0); + const start = source.lastIndexOf('function()', sink); + const end = source.indexOf('});', sink); + expect(start).toBeGreaterThan(0); + expect(end).toBeGreaterThan(sink); + const handler = source.slice(start, end + 1); + await context.route('https://cacti.net/**', route => route.fulfill({ + contentType: 'text/html', body: '<!DOCTYPE html><title>Fixture homepage', + })); + await page.setContent(''); + // Execute the repository-owned callback with a real click; no fixture + // implementation of window.open or opener isolation is substituted. + await page.addScriptTag({ content: `document.querySelector('#homepage').addEventListener('click', ${handler});` }); + const popupPromise = context.waitForEvent('page'); + await page.locator('#homepage').click(); + const popup = await popupPromise; + await popup.waitForLoadState(); + expect(await popup.evaluate(() => window.opener)).toBeNull(); +}); diff --git a/tests/e2e/tests/develop-menu-state.spec.ts b/tests/e2e/tests/develop-menu-state.spec.ts new file mode 100644 index 000000000..1919d02b1 --- /dev/null +++ b/tests/e2e/tests/develop-menu-state.spec.ts @@ -0,0 +1,67 @@ +/* Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later. */ +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const root = resolve(__dirname, '../../..'); +const layout = readFileSync(resolve(root, 'include/layout.js'), 'utf8'); +const start = layout.indexOf('$.fn.rebind = function'); +const body = layout.indexOf('{', start); +let depth = 0; +let end = body; +for (; end < layout.length; end++) { + if (layout[end] === '{') depth++; + if (layout[end] === '}' && --depth === 0) break; +} +const rebind = layout.slice(start, end + 1) + ';'; + +for (const theme of ['cacti', 'carrot', 'dark', 'deepness', 'hollyberry', 'modern', 'paper-plane', 'paw', 'raspberry', 'sunrise']) { + for (const stored of [false, true]) { + test(`${theme} navigation keeps menu state local with ${stored ? 'persisted' : 'empty'} storage`, async ({ page }) => { + await page.route('http://cacti.test/**', route => route.fulfill({ + contentType: 'text/html', + body: '', + })); + await page.goto('http://cacti.test/'); + await page.addScriptTag({ path: resolve(root, 'include/js/jquery.js') }); + await page.addScriptTag({ path: resolve(root, 'include/js/js.storage.js') }); + await page.addScriptTag({ content: rebind }); + const source = readFileSync(resolve(root, `include/themes/${theme}/main.js`), 'utf8'); + const menu = source.indexOf('function setMenuVisibility() {'); + expect(menu).toBeGreaterThan(0); + await page.addScriptTag({ content: source.slice(menu) }); + await page.evaluate(stored => { + const host = window as any; + host.jQuery.fx.off = true; + for (const name of ['id', 'active', 'text', 'storage']) host[name] = `host-${name}`; + if (stored) { + host.Storages.sessionStorage.set('menu-a', 'active'); + host.Storages.sessionStorage.set('menu-b', 'collapsed'); + } + host.setMenuVisibility(); + host.setMenuVisibility(); + }, stored); + const menuB = page.locator('#menu-b > ul'); + await expect(menuB).toBeHidden(); + await page.locator('#menu-b > a').click(); + await expect(menuB).toBeVisible(); + await expect(menuB).toHaveAttribute('aria-expanded', 'true'); + await expect(menuB).toHaveAttribute('aria-hidden', 'false'); + await expect(page.locator('#menu-a > ul')).toBeHidden(); + expect(await page.evaluate(() => { + const storage = (window as any).Storages.sessionStorage; + return [storage.get('menu-a'), storage.get('menu-b')]; + })).toEqual(['collapsed', 'active']); + await page.locator('#menu-b > a').click(); + await expect(menuB).toBeHidden(); + await expect(menuB).toHaveAttribute('aria-expanded', 'false'); + await expect(menuB).toHaveAttribute('aria-hidden', 'true'); + expect(await page.evaluate(() => (window as any).Storages.sessionStorage.get('menu-b'))).toBe('collapsed'); + expect(await page.evaluate(() => ['id', 'active', 'text', 'storage'].map(name => (window as any)[name]))) + .toEqual(['host-id', 'host-active', 'host-text', 'host-storage']); + }); + } +} diff --git a/tests/e2e/tests/table-column-widths.spec.ts b/tests/e2e/tests/table-column-widths.spec.ts new file mode 100644 index 000000000..01bf84d92 --- /dev/null +++ b/tests/e2e/tests/table-column-widths.spec.ts @@ -0,0 +1,49 @@ +/* Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later. */ +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +test('column widths follow the table attribute across action changes and retain the ID fallback', async ({ page }) => { + const root = resolve(__dirname, '../../..'); + const source = readFileSync(resolve(root, 'include/layout.js'), 'utf8'); + const start = source.indexOf('function saveTableWidths(initial)'); + const end = source.indexOf('\n}\n', start); + expect(start).toBeGreaterThan(0); + expect(end).toBeGreaterThan(start); + await page.route('http://cacti.test/column-widths', route => route.fulfill({ + contentType: 'text/html', + body: '
NameCount
', + })); + await page.goto('http://cacti.test/column-widths'); + await page.addScriptTag({ path: resolve(root, 'include/js/jquery.js') }); + await page.addScriptTag({ path: resolve(root, 'include/js/js.storage.js') }); + await page.addScriptTag({ content: source.slice(start, end + 2) }); + await page.evaluate(() => { + const browser = window as any; + browser.Storages.localStorage.set('hosts', [browser.$(document).width(), 210, 180]); + browser.saveTableWidths(true); + }); + expect(await page.locator('th').first().evaluate(element => element.getAttribute('resizeWidth'))).toBe('210'); + await page.evaluate(() => { + const browser = window as any; + document.querySelector('table')!.id = 'hosts_edit_report'; + browser.$('th').css('width', 50); + browser.saveTableWidths(true); + }); + expect(await page.locator('th').first().evaluate(element => element.getAttribute('resizeWidth'))).toBe('210'); + await page.evaluate(() => { + const browser = window as any; + const table = document.querySelector('table')!; + table.removeAttribute('data-colsizing'); + browser.Storages.localStorage.set(table.id, [browser.$(document).width(), 260, 190]); + browser.saveTableWidths(true); + }); + expect(await page.locator('th').first().evaluate(element => element.getAttribute('resizeWidth'))).toBe('260'); + await page.evaluate(() => { + const browser = window as any; + document.querySelector('table')!.setAttribute('data-colsizing', ''); + browser.saveTableWidths(true); + }); + expect(await page.locator('th').first().evaluate(element => element.getAttribute('resizeWidth'))).toBe('260'); + expect(await page.evaluate(() => Object.hasOwn(window, 'sizes') || Object.hasOwn(window, 'items'))).toBe(false); +}); diff --git a/tests/fixtures/Core/PublicMethodContractProbe.php b/tests/fixtures/Core/PublicMethodContractProbe.php new file mode 100644 index 000000000..9884be8e9 --- /dev/null +++ b/tests/fixtures/Core/PublicMethodContractProbe.php @@ -0,0 +1,49 @@ +getMethods() as $method) { + if ($method->getDeclaringClass()->getName() !== $class) { + continue; + } + + $parameters = []; + + foreach ($method->getParameters() as $parameter) { + $parameters[] = [ + 'name' => $parameter->getName(), + 'reference' => $parameter->isPassedByReference(), + 'variadic' => $parameter->isVariadic(), + 'type' => $parameter->hasType() ? (string) $parameter->getType() : null, + 'optional' => $parameter->isOptional(), + 'default' => !$parameter->isDefaultValueAvailable() ? null : + ($parameter->isDefaultValueConstant() ? + ['constant' => $parameter->getDefaultValueConstantName()] : + ['value' => $parameter->getDefaultValue()]), + ]; + } + + $contracts[$class . '::' . $method->getName()] = [ + 'public' => $method->isPublic(), + 'static' => $method->isStatic(), + 'parameters' => $parameters, + 'return' => $method->hasReturnType() ? (string) $method->getReturnType() : null, + ]; + } +} + +print json_encode($contracts, JSON_THROW_ON_ERROR); diff --git a/tests/fixtures/Core/public-method-signatures.json b/tests/fixtures/Core/public-method-signatures.json new file mode 100644 index 000000000..396684997 --- /dev/null +++ b/tests/fixtures/Core/public-method-signatures.json @@ -0,0 +1 @@ +{"Net_Ping::__construct":{"public":true,"static":false,"parameters":[],"return":null},"Net_Ping::__destruct":{"public":true,"static":false,"parameters":[],"return":null},"Net_Ping::close_socket":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::start_time":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::get_time":{"public":true,"static":false,"parameters":[{"name":"acc","reference":false,"variadic":false,"type":"int","optional":true,"default":{"value":2}}],"return":"mixed"},"Net_Ping::build_udp_packet":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::ping_error_handler":{"public":true,"static":false,"parameters":[{"name":"errno","reference":false,"variadic":false,"type":"int","optional":false,"default":null},{"name":"errmsg","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"filename","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"linenum","reference":false,"variadic":false,"type":"int","optional":false,"default":null},{"name":"vars","reference":false,"variadic":false,"type":"array","optional":true,"default":{"value":[]}}],"return":"bool"},"Net_Ping::set_ping_error_handler":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::restore_cacti_error_handler":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::build_icmp_packet":{"public":true,"static":false,"parameters":[],"return":"void"},"Net_Ping::get_checksum":{"public":true,"static":false,"parameters":[{"name":"data","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"string"},"Net_Ping::ping_icmp":{"public":true,"static":false,"parameters":[],"return":"bool"},"Net_Ping::seteuid":{"public":true,"static":false,"parameters":[],"return":"int"},"Net_Ping::setuid":{"public":true,"static":false,"parameters":[{"name":"cacti_poller_account","reference":false,"variadic":false,"type":"int","optional":false,"default":null}],"return":"void"},"Net_Ping::ping_snmp":{"public":true,"static":false,"parameters":[],"return":"mixed"},"Net_Ping::get_snmp_result":{"public":true,"static":false,"parameters":[{"name":"oid","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"bool"},"Net_Ping::ping_udp":{"public":true,"static":false,"parameters":[],"return":"bool"},"Net_Ping::ping_tcp":{"public":true,"static":false,"parameters":[],"return":"bool"},"Net_Ping::ping":{"public":true,"static":false,"parameters":[{"name":"avail_method","reference":false,"variadic":false,"type":"int","optional":true,"default":{"constant":"AVAIL_SNMP_AND_PING"}},{"name":"ping_type","reference":false,"variadic":false,"type":"int","optional":true,"default":{"constant":"PING_ICMP"}},{"name":"timeout","reference":false,"variadic":false,"type":"int","optional":true,"default":{"value":500}},{"name":"retries","reference":false,"variadic":false,"type":"int","optional":true,"default":{"value":3}}],"return":"bool"},"Net_Ping::is_ipaddress":{"public":true,"static":false,"parameters":[{"name":"ip_address","reference":false,"variadic":false,"type":"string","optional":true,"default":{"value":""}}],"return":"bool"},"Net_Ping::strip_ip_address":{"public":true,"static":false,"parameters":[{"name":"ip_address","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"string"},"Ldap::__construct":{"public":true,"static":false,"parameters":[],"return":null},"Ldap::__destruct":{"public":true,"static":false,"parameters":[],"return":null},"Ldap::ErrorHandler":{"public":true,"static":false,"parameters":[{"name":"level","reference":false,"variadic":false,"type":"int","optional":false,"default":null},{"name":"message","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"file","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"line","reference":false,"variadic":false,"type":"int","optional":false,"default":null},{"name":"context","reference":false,"variadic":false,"type":"array","optional":true,"default":{"value":[]}}],"return":"bool"},"Ldap::SetLdapHandler":{"public":true,"static":false,"parameters":[],"return":"void"},"Ldap::RestoreCactiHandler":{"public":true,"static":false,"parameters":[],"return":"void"},"Ldap::RecordError":{"public":true,"static":false,"parameters":[{"name":"output","reference":false,"variadic":false,"type":"array","optional":false,"default":null},{"name":"section","reference":false,"variadic":false,"type":"string","optional":true,"default":{"value":"LDAP"}}],"return":"void"},"Ldap::Connect":{"public":true,"static":false,"parameters":[],"return":"array"},"Ldap::Authenticate":{"public":true,"static":false,"parameters":[],"return":"array"},"Ldap::GetMask":{"public":true,"static":false,"parameters":[],"return":"int"},"Ldap::ResolveGroupMemberships":{"public":true,"static":false,"parameters":[{"name":"username","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"groupNames","reference":false,"variadic":false,"type":"array","optional":false,"default":null}],"return":"?array"},"Ldap::ResolveUserDnForGroups":{"public":false,"static":false,"parameters":[{"name":"ldap_conn","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"username_esc","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"search_base","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"string"},"Ldap::IsActiveDirectory":{"public":false,"static":false,"parameters":[{"name":"ldap_conn","reference":false,"variadic":false,"type":null,"optional":false,"default":null}],"return":"bool"},"Ldap::UserInGroupByName":{"public":false,"static":false,"parameters":[{"name":"ldap_conn","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"search_base","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"username_esc","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"user_dn","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"user_dn_esc","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"group","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"is_ad","reference":false,"variadic":false,"type":"bool","optional":false,"default":null}],"return":"?bool"},"Ldap::BuildGroupMembershipQueries":{"public":false,"static":false,"parameters":[{"name":"search_base","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"username_esc","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"user_dn","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"user_dn_esc","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"group","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"is_ad","reference":false,"variadic":false,"type":"bool","optional":false,"default":null}],"return":"array"},"Ldap::Search":{"public":true,"static":false,"parameters":[],"return":"array"},"Ldap::Getcn":{"public":true,"static":false,"parameters":[],"return":"array"},"Ldap::isUserInLDAPGroup":{"public":true,"static":false,"parameters":[{"name":"ldapConn","reference":false,"variadic":false,"type":"object","optional":false,"default":null},{"name":"ldapbasedn","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"groupDN","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"ldapUser","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"bool"},"MibParser::__construct":{"public":true,"static":false,"parameters":[],"return":null},"MibParser::add_mib":{"public":true,"static":false,"parameters":[{"name":"filename","reference":false,"variadic":false,"type":"string","optional":false,"default":null},{"name":"mib_name","reference":false,"variadic":false,"type":"string","optional":false,"default":null}],"return":"void"},"MibParser::get_tokens":{"public":true,"static":false,"parameters":[{"name":"text","reference":false,"variadic":false,"type":null,"optional":false,"default":null}],"return":null},"MibParser::parse_simple_token":{"public":true,"static":false,"parameters":[{"name":"tokens","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"index","reference":true,"variadic":false,"type":null,"optional":false,"default":null},{"name":"allowed","reference":false,"variadic":false,"type":null,"optional":true,"default":{"value":null}}],"return":null},"MibParser::parse_SYNTAX_token":{"public":true,"static":false,"parameters":[{"name":"tokens","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"index","reference":true,"variadic":false,"type":null,"optional":false,"default":null}],"return":null},"MibParser::parse_bracket_token":{"public":true,"static":false,"parameters":[{"name":"tokens","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"index","reference":true,"variadic":false,"type":null,"optional":false,"default":null},{"name":"start","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"end","reference":false,"variadic":false,"type":null,"optional":false,"default":null}],"return":null},"MibParser::parse_mib":{"public":true,"static":false,"parameters":[{"name":"mibtext","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"mib_name","reference":false,"variadic":false,"type":null,"optional":false,"default":null},{"name":"full","reference":false,"variadic":false,"type":null,"optional":true,"default":{"value":false}}],"return":"void"},"MibParser::generate":{"public":true,"static":false,"parameters":[],"return":"void"}} \ No newline at end of file diff --git a/tests/js/develop_client_state.test.js b/tests/js/develop_client_state.test.js new file mode 100644 index 000000000..75c9343a0 --- /dev/null +++ b/tests/js/develop_client_state.test.js @@ -0,0 +1,205 @@ +/* Copyright (C) 2004-2026 The Cacti Group. GPL-2.0-or-later. */ +'use strict'; + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); +const vm = require('node:vm'); +const root = path.resolve(__dirname, '../..'); + +function source(file) { + return fs.readFileSync(path.join(root, file), 'utf8'); +} + +function functionAt(text, start) { + assert.ok(start >= 0); + const body = text.indexOf('{', start); + let depth = 0; + for (let offset = body; offset < text.length; offset++) { + if (text[offset] === '{') depth++; + if (text[offset] === '}' && --depth === 0) return text.slice(start, offset + 1); + } + throw new Error('Unclosed production function'); +} + +function load(file, names, globals) { + const text = source(file); + const context = vm.createContext(globals); + for (const name of names) { + vm.runInContext(functionAt(text, text.indexOf(`function ${name}(`)), context); + } + return context; +} + +test('fullscreen controls reconcile state after successful and rejected requests', async () => { + for (const fullscreen of [false, true]) { + for (const rejected of [false, true]) { + let entries = 0; + let exits = 0; + let updates = 0; + const result = () => rejected ? Promise.reject(new Error('Browser denied request')) : Promise.resolve(); + const context = load('include/themes/midwinter/main.js', ['fullScreen'], { + document: { + fullscreenElement: fullscreen, + documentElement: { requestFullscreen: () => { entries++; return result(); } }, + exitFullscreen: () => { exits++; return result(); }, + }, + fullScreenChangeHandler: () => { updates++; }, + }); + context.fullScreen({}); + await new Promise(resolve => setImmediate(resolve)); + assert.equal(entries, fullscreen ? 0 : 1); + assert.equal(exits, fullscreen ? 1 : 0); + assert.equal(updates, 1); + } + } + const context = load('include/themes/midwinter/main.js', ['fullScreen'], { + document: { fullscreenElement: true }, + }); + assert.doesNotThrow(() => context.fullScreen({})); +}); + +test('toast accepts primitive and boxed strings without leaking global state', () => { + const text = source('include/js/ui-notices.js'); + const start = text.indexOf('function(options)'); + let rendered; + const jquery = { + extend: (...options) => Object.assign({}, ...options), + toast: options => { rendered = options; return options; }, + }; + const toast = vm.runInNewContext(`(${functionAt(text, start)})`, { $: jquery, String }); + for (const value of ['plain', new String('boxed'), { text: 'configured' }, undefined]) { + toast(value); + assert.equal(typeof rendered.text, 'string'); + assert.equal(rendered.text, value === undefined ? 'No text was set, please check the usage of showToast' : typeof value === 'object' && !(value instanceof String) ? value.text : String(value)); + } +}); + +test('realtime requests stop Pace for countdown, initial load and refresh', () => { + for (const action of ['countdown', 'initial', 'refresh']) { + let stops = 0; + let requested; + const jquery = selector => ({ + val: () => ({ '#graph_start': '60', '#ds_step': '5', '#size': '50', '#local_graph_id': '42' })[selector], + is: () => false, width: () => 600, height: () => 400, + }); + jquery.getJSON = url => { requested = url; return { done: () => ({ fail: () => {} }) }; }; + const context = load('include/realtime.js', ['imageOptionsChanged'], { + $: jquery, window: {}, Pace: { stop: () => { stops++; } }, + count: 0, rtWidth: 0, rtHeight: 0, local_graph_id: null, + }); + context.imageOptionsChanged(action); + assert.equal(stops, 1); + assert.match(requested, new RegExp(`action=${action}&`)); + assert.match(requested, /local_graph_id=42&/); + } +}); + +test('Sunrise scroll indicator covers horizontal and vertical scrolling', () => { + const text = source('include/themes/sunrise/main.js'); + const marker = ".rebind('scroll', "; + const start = text.indexOf(marker) + marker.length; + for (const x of [0, 10]) { + for (const y of [0, 10]) { + let color; + const callback = vm.runInNewContext(`(${functionAt(text, start)})`, { + $: selector => selector === '#navigation_right' + ? { scrollLeft: () => x, scrollTop: () => y } + : { css: styles => { color = styles.color; } }, + }); + callback({}); + assert.equal(color, x === 0 && y === 0 ? '' : '#93CEFF'); + } + } +}); + +test('navigation membership handles the console, matching and absent links', () => { + const jquery = () => ({ find: () => ({ each: callback => { + for (const href of ['host.php', 'graphs.php']) callback.call({ href }); + } }) }); + const context = load('include/layout.js', ['userMenuNavigationExists'], { + $: value => typeof value === 'object' ? { attr: () => value.href } : jquery(), + basename: value => value.split('/').pop(), + }); + for (const [url, expected] of [['index.php', true], ['/cacti/index.php', true], ['graphs.php', true], ['missing.php', false]]) { + assert.equal(context.userMenuNavigationExists(url), expected); + } +}); + +test('wide tables restore hidden cells even when their column indexes exceed nine', () => { + const headers = Array.from({ length: 12 }, (_, index) => ({ + index, visible: [0, 1, 10, 11].includes(index), + locked: [0, 10].includes(index), checkbox: index === 11, + })); + const cells = headers.map((header) => ({ index: header.index, visible: header.visible })); + const table = { table: true }; + function collection(nodes) { + return { + length: nodes.length, + get: () => nodes, + each: (callback) => { + nodes.forEach((node, index) => callback.call(node, index, node)); + return collection(nodes); + }, + find: (selector) => collection(selector === 'th' ? headers : selector === 'td' ? cells : selector === 'tr' ? [{}, {}] : []), + width: () => 300, + attr: () => 'table-fixture', + index: () => nodes[0].index, + css: (property) => property === 'display' ? (nodes[0].visible ? 'table-cell' : 'none') : undefined, + hasClass: (name) => name === 'noHide' ? nodes[0].locked : name === 'tableSubHeaderCheckbox' && nodes[0].checkbox, + is: (selector) => selector === ':visible' ? nodes[0].visible : !nodes[0].visible, + show: () => { nodes.forEach((node) => { node.visible = true; }); }, + hide: () => { nodes.forEach((node) => { node.visible = false; }); }, + }; + } + const jquery = (value) => value && typeof value.each === 'function' ? value : collection(Array.isArray(value) ? value : [value]); + jquery.textMetrics = () => ({ width: 5 }); + const context = load('include/layout.js', ['countHiddenCols', 'tuneTable'], { $: jquery, hScroll: false }); + context.tuneTable(table, 500); + assert.equal(headers.every((header) => header.visible), true); + assert.equal(cells.every((cell) => cell.visible), true); +}); + +test('realtime shutdown restores snapshots populated by the layout click handler', () => { + const initial = 'Traffic graph'; + let content = initial; + let filters = 0; + const link = {}; + function jquery(selector) { + const chain = { + attr: () => 'graph_42_realtime', + html: (value) => { + if (selector === '#wrapper_42') { + if (value === undefined) return content; + content = value; + } + return chain; + }, + trigger: () => chain, empty: () => chain, find: () => chain, + tooltip: () => chain, children: () => chain, on: () => chain, + zoom: () => chain, remove: () => chain, css: () => chain, + }; + return chain; + } + const context = load('include/realtime.js', ['stopRealtime'], { + $: jquery, realtimeArray: [], keepRealtime: [], timeOffset: 0, + realtimeClickOn: 'Start', realtimeClickOff: 'Stop', urlPath: '', + setFilters: () => { filters++; }, tuneFilter: () => {}, realtimeGrapher: () => {}, shouldCaptureClick: () => true, + }); + const layout = functionAt(source('include/layout.js'), source('include/layout.js').indexOf('function initializeGraphs(')); + const marker = "$(this).rebind('click', function (event) {"; + const scope = layout.indexOf("$('a[id$=\"_realtime\"]')"); + assert.notEqual(scope, -1); + const start = layout.indexOf(marker, scope) + marker.indexOf('function (event)'); + assert.ok(start > 0); + const click = vm.runInContext(`(${functionAt(layout, start)})`, context); + click.call(link, { preventDefault: () => {}, stopPropagation: () => {} }); + assert.equal(context.realtimeArray[42], true); + assert.equal(context.keepRealtime[42], initial); + content = 'realtime frame'; + context.stopRealtime(); + assert.equal(content, initial); + assert.equal(context.realtimeArray[42], false); + assert.equal(filters, 2); +});