// Main JavaScript file for the Netdata GUI. // Codacy declarations /* global NETDATA */ // netdata snapshot data var netdataSnapshotData = null; // enable alarms checking and notifications var netdataShowAlarms = true; // enable registry updates var netdataRegistry = true; // forward definition only - not used here var netdataServer = undefined; var netdataServerStatic = undefined; var netdataCheckXSS = undefined; // control the welcome modal and analytics var this_is_demo = null; function escapeUserInputHTML(s) { return s.toString() .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/#/g, '#') .replace(/'/g, ''') .replace(/\(/g, '(') .replace(/\)/g, ')') .replace(/\//g, '/'); } function verifyURL(s) { if (typeof (s) === 'string' && (s.startsWith('http://') || s.startsWith('https://'))) { return s .replace(/'/g, '%22') .replace(/"/g, '%27') .replace(/\)/g, '%28') .replace(/\(/g, '%29'); } console.log('invalid URL detected:'); console.log(s); return 'javascript:alert("invalid url");'; } // -------------------------------------------------------------------- // urlOptions var urlOptions = { hash: '#', theme: null, help: null, mode: 'live', // 'live', 'print' update_always: false, pan_and_zoom: false, server: null, after: 0, before: 0, highlight: false, highlight_after: 0, highlight_before: 0, nowelcome: false, show_alarms: false, chart: null, family: null, alarm: null, alarm_unique_id: 0, alarm_id: 0, alarm_event_id: 0, alarm_when: 0, hasProperty: function (property) { // console.log('checking property ' + property + ' of type ' + typeof(this[property])); return typeof this[property] !== 'undefined'; }, genHash: function (forReload) { var hash = urlOptions.hash; if (urlOptions.pan_and_zoom === true) { hash += ';after=' + urlOptions.after.toString() + ';before=' + urlOptions.before.toString(); } if (urlOptions.highlight === true) { hash += ';highlight_after=' + urlOptions.highlight_after.toString() + ';highlight_before=' + urlOptions.highlight_before.toString(); } if (urlOptions.theme !== null) { hash += ';theme=' + urlOptions.theme.toString(); } if (urlOptions.help !== null) { hash += ';help=' + urlOptions.help.toString(); } if (urlOptions.update_always === true) { hash += ';update_always=true'; } if (forReload === true && urlOptions.server !== null) { hash += ';server=' + urlOptions.server.toString(); } if (urlOptions.mode !== 'live') { hash += ';mode=' + urlOptions.mode; } return hash; }, parseHash: function () { var variables = document.location.hash.split(';'); var len = variables.length; while (len--) { if (len !== 0) { var p = variables[len].split('='); if (urlOptions.hasProperty(p[0]) && typeof p[1] !== 'undefined') { urlOptions[p[0]] = decodeURIComponent(p[1]); } } else { if (variables[len].length > 0) { urlOptions.hash = variables[len]; } } } var booleans = ['nowelcome', 'show_alarms', 'update_always']; len = booleans.length; while (len--) { if (urlOptions[booleans[len]] === 'true' || urlOptions[booleans[len]] === true || urlOptions[booleans[len]] === '1' || urlOptions[booleans[len]] === 1) { urlOptions[booleans[len]] = true; } else { urlOptions[booleans[len]] = false; } } var numeric = ['after', 'before', 'highlight_after', 'highlight_before', 'alarm_when']; len = numeric.length; while (len--) { if (typeof urlOptions[numeric[len]] === 'string') { try { urlOptions[numeric[len]] = parseInt(urlOptions[numeric[len]]); } catch (e) { console.log('failed to parse URL hash parameter ' + numeric[len]); urlOptions[numeric[len]] = 0; } } } if (urlOptions.alarm_when) { // if alarm_when exists, create after/before params // -/+ 2 minutes from the alarm, and reload the page const alarmTime = new Date(urlOptions.alarm_when * 1000).valueOf(); const timeMarginMs = 120000; // 2 mins const after = alarmTime - timeMarginMs; const before = alarmTime + timeMarginMs; const newHash = document.location.hash.replace( /;alarm_when=[0-9]*/i, ";after=" + after + ";before=" + before, ); history.replaceState(null, '', newHash); location.reload(); } if (urlOptions.server !== null && urlOptions.server !== '') { netdataServerStatic = document.location.origin.toString() + document.location.pathname.toString(); netdataServer = urlOptions.server; netdataCheckXSS = true; } else { urlOptions.server = null; } if (urlOptions.before > 0 && urlOptions.after > 0) { urlOptions.pan_and_zoom = true; urlOptions.nowelcome = true; } else { urlOptions.pan_and_zoom = false; } if (urlOptions.highlight_before > 0 && urlOptions.highlight_after > 0) { urlOptions.highlight = true; } else { urlOptions.highlight = false; } switch (urlOptions.mode) { case 'print': urlOptions.theme = 'white'; urlOptions.welcome = false; urlOptions.help = false; urlOptions.show_alarms = false; if (urlOptions.pan_and_zoom === false) { urlOptions.pan_and_zoom = true; urlOptions.before = Date.now(); urlOptions.after = urlOptions.before - 600000; } netdataShowAlarms = false; netdataRegistry = false; this_is_demo = false; break; case 'live': default: urlOptions.mode = 'live'; break; } // console.log(urlOptions); }, hashUpdate: function () { history.replaceState(null, '', urlOptions.genHash(true)); }, netdataPanAndZoomCallback: function (status, after, before) { //console.log(1); //console.log(new Error().stack); if (netdataSnapshotData === null) { urlOptions.pan_and_zoom = status; urlOptions.after = after; urlOptions.before = before; urlOptions.hashUpdate(); } }, netdataHighlightCallback: function (status, after, before) { //console.log(2); //console.log(new Error().stack); if (status === true && (after === null || before === null || after <= 0 || before <= 0 || after >= before)) { status = false; after = 0; before = 0; } if (netdataSnapshotData === null) { urlOptions.highlight = status; } else { urlOptions.highlight = false; } urlOptions.highlight_after = Math.round(after); urlOptions.highlight_before = Math.round(before); urlOptions.hashUpdate(); var show_eye = NETDATA.globalChartUnderlay.hasViewport(); if (status === true && after > 0 && before > 0 && after < before) { var d1 = NETDATA.dateTime.localeDateString(after); var d2 = NETDATA.dateTime.localeDateString(before); if (d1 === d2) { d2 = ''; } document.getElementById('navbar-highlight-content').innerHTML = ((show_eye === true) ? '' : '').toString() + 'highlighted time-frame' + ' ' + d1 + ' ' + NETDATA.dateTime.localeTimeString(after) + ' to ' + ' ' + d2 + ' ' + NETDATA.dateTime.localeTimeString(before) + ', ' + 'duration ' + NETDATA.seconds4human(Math.round((before - after) / 1000)) + '' + '' + ''; $('.navbar-highlight').show(); $('.highlight-tooltip').tooltip({ html: true, delay: { show: 500, hide: 0 }, container: 'body' }); } else { $('.navbar-highlight').hide(); } }, clearHighlight: function () { NETDATA.globalChartUnderlay.clear(); if (NETDATA.globalPanAndZoom.isActive() === true) { NETDATA.globalPanAndZoom.clearMaster(); } }, showHighlight: function () { NETDATA.globalChartUnderlay.focus(); } }; urlOptions.parseHash(); // -------------------------------------------------------------------- // check options that should be processed before loading netdata.js var localStorageTested = -1; function localStorageTest() { if (localStorageTested !== -1) { return localStorageTested; } if (typeof Storage !== "undefined" && typeof localStorage === 'object') { var test = 'test'; try { localStorage.setItem(test, test); localStorage.removeItem(test); localStorageTested = true; } catch (e) { console.log(e); localStorageTested = false; } } else { localStorageTested = false; } return localStorageTested; } function loadLocalStorage(name) { var ret = null; try { if (localStorageTest() === true) { ret = localStorage.getItem(name); } else { console.log('localStorage is not available'); } } catch (error) { console.log(error); return null; } if (typeof ret === 'undefined' || ret === null) { return null; } // console.log('loaded: ' + name.toString() + ' = ' + ret.toString()); return ret; } function saveLocalStorage(name, value) { // console.log('saving: ' + name.toString() + ' = ' + value.toString()); try { if (localStorageTest() === true) { localStorage.setItem(name, value.toString()); return true; } } catch (error) { console.log(error); } return false; } function getTheme(def) { if (urlOptions.mode === 'print') { return 'white'; } var ret = loadLocalStorage('netdataTheme'); if (typeof ret === 'undefined' || ret === null || ret === 'undefined') { return def; } else { return ret; } } function setTheme(theme) { if (urlOptions.mode === 'print') { return false; } if (theme === netdataTheme) { return false; } return saveLocalStorage('netdataTheme', theme); } var netdataTheme = getTheme('slate'); var netdataShowHelp = true; if (urlOptions.theme !== null) { setTheme(urlOptions.theme); netdataTheme = urlOptions.theme; } else { urlOptions.theme = netdataTheme; } if (urlOptions.help !== null) { saveLocalStorage('options.show_help', urlOptions.help); netdataShowHelp = urlOptions.help; } else { urlOptions.help = loadLocalStorage('options.show_help'); } // -------------------------------------------------------------------- // natural sorting // http://www.davekoelle.com/files/alphanum.js - LGPL function naturalSortChunkify(t) { var tz = []; var x = 0, y = -1, n = 0, i, j; while (i = (j = t.charAt(x++)).charCodeAt(0)) { var m = (i >= 48 && i <= 57); if (m !== n) { tz[++y] = ""; n = m; } tz[y] += j; } return tz; } function naturalSortCompare(a, b) { var aa = naturalSortChunkify(a.toLowerCase()); var bb = naturalSortChunkify(b.toLowerCase()); for (var x = 0; aa[x] && bb[x]; x++) { if (aa[x] !== bb[x]) { var c = Number(aa[x]), d = Number(bb[x]); if (c.toString() === aa[x] && d.toString() === bb[x]) { return c - d; } else { return (aa[x] > bb[x]) ? 1 : -1; } } } return aa.length - bb.length; } // -------------------------------------------------------------------- // saving files to client function saveTextToClient(data, filename) { var blob = new Blob([data], { type: 'application/octet-stream' }); var url = URL.createObjectURL(blob); var link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', filename); var el = document.getElementById('hiddenDownloadLinks'); el.innerHTML = ''; el.appendChild(link); setTimeout(function () { el.removeChild(link); URL.revokeObjectURL(url); }, 60); link.click(); } function saveObjectToClient(data, filename) { saveTextToClient(JSON.stringify(data), filename); } // ----------------------------------------------------------------------------- // registry call back to render my-netdata menu function toggleExpandIcon(svgEl) { if (svgEl.getAttribute('data-icon') === 'caret-down') { svgEl.setAttribute('data-icon', 'caret-up'); } else { svgEl.setAttribute('data-icon', 'caret-down'); } } function toggleAgentItem(e, guid) { e.stopPropagation(); e.preventDefault(); toggleExpandIcon(e.currentTarget.children[0]); const el = document.querySelector(`.agent-alternate-urls.agent-${guid}`); if (el) { el.classList.toggle('collapsed'); } } // When you stream metrics from netdata to netdata, the receiving netdata now // has multiple host databases. It's own, and multiple mirrored. Mirrored databases // can be accessed with const OLD_DASHBOARD_SUFFIX = "old" let isOldSuffix = true try { const currentScriptMainJs = document.currentScript; const mainJsSrc = currentScriptMainJs.getAttribute("src") isOldSuffix = mainJsSrc.startsWith("../main.js") } catch { console.warn("current script not detecting, assuming the dashboard is running with /old suffix") } function transformWithOldSuffix(url) { return isOldSuffix ? `../${url}` : url } function renderStreamedHosts(options) { let html = `
Databases streamed to this agent
`; var base = document.location.origin.toString() + document.location.pathname.toString() .replace(isOldSuffix ? `/${OLD_DASHBOARD_SUFFIX}` : "", ""); if (base.endsWith("/host/" + options.hostname + "/")) { base = base.substring(0, base.length - ("/host/" + options.hostname + "/").toString().length); } if (base.endsWith("/")) { base = base.substring(0, base.length - 1); } var master = options.hosts[0].hostname; // We sort a clone of options.hosts, to keep the master as the first element // for future calls. var sorted = options.hosts.slice(0).sort(function (a, b) { if (a.hostname === master) { return -1; } return naturalSortCompare(a.hostname, b.hostname); }); let displayedDatabases = false; for (var s of sorted) { let url, icon; const hostname = s.hostname; if (myNetdataMenuFilterValue !== "") { if (!hostname.includes(myNetdataMenuFilterValue)) { continue; } } displayedDatabases = true; if (hostname === master) { url = isOldSuffix ? `${base}/${OLD_DASHBOARD_SUFFIX}/` : `${base}/`; icon = 'home'; } else { url = isOldSuffix ? `${base}/host/${hostname}/${OLD_DASHBOARD_SUFFIX}/` : `${base}/host/${hostname}/`; icon = 'window-restore'; } html += ( `` ) } if (!displayedDatabases) { html += ( `
no databases match the filter criteria.
` ) } return html; } function renderMachines(machinesArray) { let html = `
My nodes
`; if (machinesArray === null) { let ret = loadLocalStorage("registryCallback"); if (ret) { machinesArray = JSON.parse(ret); console.log("failed to contact the registry - loaded registry data from browser local storage"); } } let found = false; let displayedAgents = false; const maskedURL = NETDATA.registry.MASKED_DATA; if (machinesArray) { saveLocalStorage("registryCallback", JSON.stringify(machinesArray)); var machines = machinesArray.sort(function (a, b) { return naturalSortCompare(a.name, b.name); }); for (var machine of machines) { found = true; if (myNetdataMenuFilterValue !== "") { if (!machine.name.includes(myNetdataMenuFilterValue)) { continue; } } displayedAgents = true; const alternateUrlItems = ( `` ) html += ( ` ${alternateUrlItems}` ) } if (found && (!displayedAgents)) { html += ( `
zero nodes are matching the filter value.
` ) } } if (!found) { if (machines) { html += ( `` ) } else { html += ( `` ) } html += `
`; html += `
Demo netdata nodes
`; const demoServers = [ { url: "//london.netdata.rocks/default.html", title: "UK - London (DigitalOcean.com)" }, { url: "//newyork.netdata.rocks/default.html", title: "US - New York (DigitalOcean.com)" }, { url: "//sanfrancisco.netdata.rocks/default.html", title: "US - San Francisco (DigitalOcean.com)" }, { url: "//atlanta.netdata.rocks/default.html", title: "US - Atlanta (CDN77.com)" }, { url: "//frankfurt.netdata.rocks/default.html", title: "Germany - Frankfurt (DigitalOcean.com)" }, { url: "//toronto.netdata.rocks/default.html", title: "Canada - Toronto (DigitalOcean.com)" }, { url: "//singapore.netdata.rocks/default.html", title: "Japan - Singapore (DigitalOcean.com)" }, { url: "//bangalore.netdata.rocks/default.html", title: "India - Bangalore (DigitalOcean.com)" }, ] for (var server of demoServers) { html += ( ` ` ); } } return html; } function setMyNetdataMenu(html) { const el = document.getElementById('my-netdata-dropdown-content') el.innerHTML = html; } function clearMyNetdataMenu() { setMyNetdataMenu(`
Loading, please wait...
`); } function errorMyNetdataMenu() { setMyNetdataMenu(`
Cannot load known Netdata agents from Netdata Cloud! Please make sure you have the latest version of Netdata.
`); } function restrictMyNetdataMenu() { setMyNetdataMenu(`
Please sign in to netdata.cloud to view your nodes!
`); } function openAuthenticatedUrl(url) { if (isSignedIn()) { window.open(url); } else { window.open(`${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}&redirect_uri=${encodeURIComponent(window.location.origin + "/" + url)}`); } } function renderMyNetdataMenu(machinesArray) { const el = document.getElementById('my-netdata-dropdown-content'); el.classList.add(`theme-${netdataTheme}`); if (machinesArray == registryAgents) { console.log("Rendering my-netdata menu from registry"); } else { console.log("Rendering my-netdata menu from netdata.cloud", machinesArray); } let html = ''; if (!isSignedIn()) { if (!NETDATA.registry.isRegistryEnabled()) { html += ( `
Please sign in to netdata.cloud to view your nodes!

` ); } } if (isSignedIn()) { html += ( `

` ); } // options.hosts = [ // { // hostname: "streamed1", // }, // { // hostname: "streamed2", // }, // ] if (options.hosts.length > 1) { html += `
${renderStreamedHosts(options)}

`; } if (isSignedIn() || NETDATA.registry.isRegistryEnabled()) { html += `
${renderMachines(machinesArray)}

`; } if (!isSignedIn()) { html += ( ` ` ) } else { html += ( ` ` ) } el.innerHTML = html; gotoServerInit(); } function isdemo() { if (this_is_demo !== null) { return this_is_demo; } this_is_demo = false; try { if (typeof document.location.hostname === 'string') { if (document.location.hostname.endsWith('.my-netdata.io') || document.location.hostname.endsWith('.mynetdata.io') || document.location.hostname.endsWith('.netdata.rocks') || document.location.hostname.endsWith('.netdata.ai') || document.location.hostname.endsWith('.netdata.live') || document.location.hostname.endsWith('.firehol.org') || document.location.hostname.endsWith('.netdata.online') || document.location.hostname.endsWith('.netdata.cloud')) { this_is_demo = true; } } } catch (error) { } return this_is_demo; } function netdataURL(url, forReload) { if (typeof url === 'undefined') // url = document.location.toString(); { url = ''; } if (url.indexOf('#') !== -1) { url = url.substring(0, url.indexOf('#')); } var hash = urlOptions.genHash(forReload); // console.log('netdataURL: ' + url + hash); return url + hash; } function netdataReload(url) { document.location = verifyURL(netdataURL(url, true)); // since we play with hash // this is needed to reload the page location.reload(); } function gotoHostedModalHandler(url) { document.location = verifyURL(url + urlOptions.genHash()); return false; } var gotoServerValidateRemaining = 0; var gotoServerMiddleClick = false; var gotoServerStop = false; function gotoServerValidateUrl(id, guid, url) { var penalty = 0; var error = 'failed'; if (document.location.toString().startsWith('http://') && url.toString().startsWith('https://')) // we penalize https only if the current url is http // to allow the user walk through all its servers. { penalty = 500; } else if (document.location.toString().startsWith('https://') && url.toString().startsWith('http://')) { error = 'can\'t check'; } var finalURL = netdataURL(url); setTimeout(function () { document.getElementById('gotoServerList').innerHTML += '' + escapeUserInputHTML(url) + 'checking...'; NETDATA.registry.hello(url, function (data) { if (typeof data !== 'undefined' && data !== null && typeof data.machine_guid === 'string' && data.machine_guid === guid) { // console.log('OK ' + id + ' URL: ' + url); document.getElementById(guid + '-' + id + '-status').innerHTML = "OK"; if (!gotoServerStop) { gotoServerStop = true; if (gotoServerMiddleClick) { window.open(verifyURL(finalURL), '_blank'); gotoServerMiddleClick = false; document.getElementById('gotoServerResponse').innerHTML = 'Opening new window to ' + NETDATA.registry.machines[guid].name + '
' + escapeUserInputHTML(url) + '

(check your pop-up blocker if it fails)'; } else { document.getElementById('gotoServerResponse').innerHTML += 'found it! It is at:
' + escapeUserInputHTML(url) + ''; document.location = verifyURL(finalURL); $('#gotoServerModal').modal('hide'); } } } else { if (typeof data !== 'undefined' && data !== null && typeof data.machine_guid === 'string' && data.machine_guid !== guid) { error = 'wrong machine'; } document.getElementById(guid + '-' + id + '-status').innerHTML = error; gotoServerValidateRemaining--; if (gotoServerValidateRemaining <= 0) { gotoServerMiddleClick = false; document.getElementById('gotoServerResponse').innerHTML = 'Sorry! I cannot find any operational URL for this server'; } } }); }, (id * 50) + penalty); } function gotoServerModalHandler(guid) { // console.log('goto server: ' + guid); gotoServerStop = false; var checked = {}; var len = NETDATA.registry.machines[guid].alternate_urls.length; var count = 0; document.getElementById('gotoServerResponse').innerHTML = ''; document.getElementById('gotoServerList').innerHTML = ''; document.getElementById('gotoServerName').innerHTML = NETDATA.registry.machines[guid].name; $('#gotoServerModal').modal('show'); gotoServerValidateRemaining = len; while (len--) { var url = NETDATA.registry.machines[guid].alternate_urls[len]; checked[url] = true; gotoServerValidateUrl(count++, guid, url); } if (!isSignedIn()) { // When the registry is enabled, if the user's known URLs are not working // we consult the registry to get additional URLs. setTimeout(function () { if (gotoServerStop === false) { document.getElementById('gotoServerResponse').innerHTML = 'Added all the known URLs for this machine.'; NETDATA.registry.search(guid, function (data) { // console.log(data); len = data.urls.length; while (len--) { var url = data.urls[len][1]; // console.log(url); if (typeof checked[url] === 'undefined') { gotoServerValidateRemaining++; checked[url] = true; gotoServerValidateUrl(count++, guid, url); } } }); } }, 2000); } return false; } function gotoServerInit() { $(".registry_link").on('click', function (e) { if (e.which === 2) { e.preventDefault(); gotoServerMiddleClick = true; } else { gotoServerMiddleClick = false; } return true; }); } function switchRegistryModalHandler() { document.getElementById('switchRegistryPersonGUID').value = NETDATA.registry.person_guid; document.getElementById('switchRegistryURL').innerHTML = NETDATA.registry.server; document.getElementById('switchRegistryResponse').innerHTML = ''; $('#switchRegistryModal').modal('show'); } function notifyForSwitchRegistry() { var n = document.getElementById('switchRegistryPersonGUID').value; if (n !== '' && n.length === 36) { NETDATA.registry.switch(n, function (result) { if (result !== null) { $('#switchRegistryModal').modal('hide'); NETDATA.registry.init(); } else { document.getElementById('switchRegistryResponse').innerHTML = "Sorry! The registry rejected your request."; } }); } else { document.getElementById('switchRegistryResponse').innerHTML = "The ID you have entered is not a GUID."; } } var deleteRegistryGuid = null; var deleteRegistryUrl = null; function deleteRegistryModalHandler(guid, name, url) { // void (guid); deleteRegistryGuid = guid; deleteRegistryUrl = url; document.getElementById('deleteRegistryServerName').innerHTML = name; document.getElementById('deleteRegistryServerName2').innerHTML = name; document.getElementById('deleteRegistryServerURL').innerHTML = url; document.getElementById('deleteRegistryResponse').innerHTML = ''; $('#deleteRegistryModal').modal('show'); } function notifyForDeleteRegistry() { const responseEl = document.getElementById('deleteRegistryResponse'); if (deleteRegistryUrl) { if (isSignedIn()) { deleteCloudAgentURL(deleteRegistryGuid, deleteRegistryUrl) .then((count) => { if (!count) { responseEl.innerHTML = "Sorry, this command was rejected by netdata.cloud!"; return; } NETDATA.registry.delete(deleteRegistryUrl, function (result) { if (result === null) { console.log("Received error from registry", result); } deleteRegistryUrl = null; $('#deleteRegistryModal').modal('hide'); NETDATA.registry.init(); }); }); } else { NETDATA.registry.delete(deleteRegistryUrl, function (result) { if (result !== null) { deleteRegistryUrl = null; $('#deleteRegistryModal').modal('hide'); NETDATA.registry.init(); } else { responseEl.innerHTML = "Sorry, this command was rejected by the registry server!"; } }); } } } var options = { menus: {}, submenu_names: {}, data: null, hostname: 'netdata_server', // will be overwritten by the netdata server version: 'unknown', release_channel: 'unknown', hosts: [], duration: 0, // the default duration of the charts update_every: 1, chartsPerRow: 0, // chartsMinWidth: 1450, chartsHeight: 180, }; function chartsPerRow(total) { void (total); if (options.chartsPerRow === 0) { return 1; //var width = Math.floor(total / options.chartsMinWidth); //if(width === 0) width = 1; //return width; } else { return options.chartsPerRow; } } function prioritySort(a, b) { if (a.priority < b.priority) { return -1; } if (a.priority > b.priority) { return 1; } return naturalSortCompare(a.name, b.name); } function sortObjectByPriority(object) { var idx = {}; var sorted = []; for (var i in object) { if (!object.hasOwnProperty(i)) { continue; } if (typeof idx[i] === 'undefined') { idx[i] = object[i]; sorted.push(i); } } sorted.sort(function (a, b) { if (idx[a].priority < idx[b].priority) { return -1; } if (idx[a].priority > idx[b].priority) { return 1; } return naturalSortCompare(a, b); }); return sorted; } // ---------------------------------------------------------------------------- // scroll to a section, without changing the browser history function scrollToId(hash) { if (hash && hash !== '' && document.getElementById(hash) !== null) { var offset = $('#' + hash).offset(); if (typeof offset !== 'undefined') { //console.log('scrolling to ' + hash + ' at ' + offset.top.toString()); $('html, body').animate({ scrollTop: offset.top - 30 }, 0); } } // we must return false to prevent the default action return false; } // ---------------------------------------------------------------------------- // user editable information var customDashboard = { menu: {}, submenu: {}, context: {} }; // netdata standard information var netdataDashboard = { sparklines_registry: {}, os: 'unknown', menu: {}, submenu: {}, context: {}, // generate a sparkline // used in the documentation sparkline: function (prefix, chart, dimension, units, suffix) { if (options.data === null || typeof options.data.charts === 'undefined') { return ''; } if (typeof options.data.charts[chart] === 'undefined') { return ''; } if (typeof options.data.charts[chart].dimensions === 'undefined') { return ''; } if (typeof options.data.charts[chart].dimensions[dimension] === 'undefined') { return ''; } var key = chart + '.' + dimension; if (typeof units === 'undefined') { units = ''; } if (typeof this.sparklines_registry[key] === 'undefined') { this.sparklines_registry[key] = { count: 1 }; } else { this.sparklines_registry[key].count++; } key = key + '.' + this.sparklines_registry[key].count; return prefix + '
(X' + units + ')' + suffix; }, gaugeChart: function (title, width, dimensions, colors) { if (typeof colors === 'undefined') { colors = ''; } if (typeof dimensions === 'undefined') { dimensions = ''; } return '
'; }, anyAttribute: function (obj, attr, key, def) { if (typeof (obj[key]) !== 'undefined') { var x = obj[key][attr]; if (typeof (x) === 'undefined') { return def; } if (typeof (x) === 'function') { return x(netdataDashboard.os); } return x; } return def; }, menuTitle: function (chart) { if (typeof chart.menu_pattern !== 'undefined') { return (this.anyAttribute(this.menu, 'title', chart.menu_pattern, chart.menu_pattern).toString() + ' ' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString()).replace(/_/g, ' '); } return (this.anyAttribute(this.menu, 'title', chart.menu, chart.menu)).toString().replace(/_/g, ' '); }, menuIcon: function (chart) { if (typeof chart.menu_pattern !== 'undefined') { return this.anyAttribute(this.menu, 'icon', chart.menu_pattern, '').toString(); } return this.anyAttribute(this.menu, 'icon', chart.menu, ''); }, menuInfo: function (chart) { if (typeof chart.menu_pattern !== 'undefined') { return this.anyAttribute(this.menu, 'info', chart.menu_pattern, null); } return this.anyAttribute(this.menu, 'info', chart.menu, null); }, menuHeight: function (chart) { if (typeof chart.menu_pattern !== 'undefined') { return this.anyAttribute(this.menu, 'height', chart.menu_pattern, 1.0); } return this.anyAttribute(this.menu, 'height', chart.menu, 1.0); }, submenuTitle: function (menu, submenu) { var key = menu + '.' + submenu; // console.log(key); var title = this.anyAttribute(this.submenu, 'title', key, submenu).toString().replace(/_/g, ' '); if (title.length > 28) { var a = title.substring(0, 13); var b = title.substring(title.length - 12, title.length); return a + '...' + b; } return title; }, submenuInfo: function (menu, submenu) { var key = menu + '.' + submenu; return this.anyAttribute(this.submenu, 'info', key, null); }, submenuHeight: function (menu, submenu, relative) { var key = menu + '.' + submenu; return this.anyAttribute(this.submenu, 'height', key, 1.0) * relative; }, contextInfo: function (id) { var x = this.anyAttribute(this.context, 'info', id, null); if (x !== null) { return '
' + x + '
'; } else { return ''; } }, contextValueRange: function (id) { if (typeof this.context[id] !== 'undefined' && typeof this.context[id].valueRange !== 'undefined') { return this.context[id].valueRange; } else { return '[null, null]'; } }, contextHeight: function (id, def) { if (typeof this.context[id] !== 'undefined' && typeof this.context[id].height !== 'undefined') { return def * this.context[id].height; } else { return def; } }, contextDecimalDigits: function (id, def) { if (typeof this.context[id] !== 'undefined' && typeof this.context[id].decimalDigits !== 'undefined') { return this.context[id].decimalDigits; } else { return def; } } }; // ---------------------------------------------------------------------------- // enrich the data structure returned by netdata // to reflect our menu system and content // TODO: this is a shame - we should fix charts naming (issue #807) function enrichChartData(chart) { var parts = chart.type.split('_'); var tmp = parts[0]; switch (tmp) { case 'ap': case 'net': case 'disk': case 'powersupply': chart.menu = tmp; break; case 'apache': chart.menu = chart.type; if (parts.length > 2 && parts[1] === 'cache') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'bind': chart.menu = chart.type; if (parts.length > 2 && parts[1] === 'rndc') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'cgroup': chart.menu = chart.type; if (chart.id.match(/.*[\._\/-:]qemu[\._\/-:]*/) || chart.id.match(/.*[\._\/-:]kvm[\._\/-:]*/)) { chart.menu_pattern = 'cgqemu'; } else { chart.menu_pattern = 'cgroup'; } break; case 'go': chart.menu = chart.type; if (parts.length > 2 && parts[1] === 'expvar') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'mount': if (parts.length > 2) { chart.menu = tmp + '_' + parts[1]; } else { chart.menu = tmp; } break; case 'isc': chart.menu = chart.type; if (parts.length > 2 && parts[1] === 'dhcpd') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'ovpn': chart.menu = chart.type; if (parts.length > 3 && parts[1] === 'status' && parts[2] === 'log') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'smartd': case 'web': chart.menu = chart.type; if (parts.length > 2 && parts[1] === 'log') { chart.menu_pattern = tmp + '_' + parts[1]; } else if (parts.length > 1) { chart.menu_pattern = tmp; } break; case 'tc': chart.menu = tmp; // find a name for this device from fireqos info // we strip '_(in|out)' or '(in|out)_' if (chart.context === 'tc.qos' && (typeof options.submenu_names[chart.family] === 'undefined' || options.submenu_names[chart.family] === chart.family)) { var n = chart.name.split('.')[1]; if (n.endsWith('_in')) { options.submenu_names[chart.family] = n.slice(0, n.lastIndexOf('_in')); } else if (n.endsWith('_out')) { options.submenu_names[chart.family] = n.slice(0, n.lastIndexOf('_out')); } else if (n.startsWith('in_')) { options.submenu_names[chart.family] = n.slice(3, n.length); } else if (n.startsWith('out_')) { options.submenu_names[chart.family] = n.slice(4, n.length); } else { options.submenu_names[chart.family] = n; } } // increase the priority of IFB devices // to have inbound appear before outbound if (chart.id.match(/.*-ifb$/)) { chart.priority--; } break; default: chart.menu = chart.type; if (parts.length > 1) { chart.menu_pattern = tmp; } break; } chart.submenu = chart.family; } // ---------------------------------------------------------------------------- function headMain(os, charts, duration) { void (os); if (urlOptions.mode === 'print') { return ''; } var head = ''; if (typeof charts['system.swap'] !== 'undefined') { head += '
'; } if (typeof charts['system.io'] !== 'undefined') { head += '
'; head += '
'; } else if (typeof charts['system.pgpgio'] !== 'undefined') { head += '
'; head += '
'; } if (typeof charts['system.cpu'] !== 'undefined') { head += '
'; } if (typeof charts['system.net'] !== 'undefined') { head += '
'; head += '
'; } else if (typeof charts['system.ip'] !== 'undefined') { head += '
'; head += '
'; } else if (typeof charts['system.ipv4'] !== 'undefined') { head += '
'; head += '
'; } else if (typeof charts['system.ipv6'] !== 'undefined') { head += '
'; head += '
'; } if (typeof charts['system.ram'] !== 'undefined') { head += '
'; } return head; } function generateHeadCharts(type, chart, duration) { if (urlOptions.mode === 'print') { return ''; } var head = ''; var hcharts = netdataDashboard.anyAttribute(netdataDashboard.context, type, chart.context, []); if (hcharts.length > 0) { var hi = 0, hlen = hcharts.length; while (hi < hlen) { if (typeof hcharts[hi] === 'function') { head += hcharts[hi](netdataDashboard.os, chart.id).replace(/CHART_DURATION/g, duration.toString()).replace(/CHART_UNIQUE_ID/g, chart.id); } else { head += hcharts[hi].replace(/CHART_DURATION/g, duration.toString()).replace(/CHART_UNIQUE_ID/g, chart.id); } hi++; } } return head; } function renderPage(menus, data) { var div = document.getElementById('charts_div'); var pcent_width = Math.floor(100 / chartsPerRow($(div).width())); // find the proper duration for per-second updates var duration = Math.round(($(div).width() * pcent_width / 100 * data.update_every / 3) / 60) * 60; options.duration = duration; options.update_every = data.update_every; var html = ''; var sidebar = ''; div.innerHTML = html; document.getElementById('sidebar').innerHTML = sidebar; if (urlOptions.highlight === true) { NETDATA.globalChartUnderlay.init(null , urlOptions.highlight_after , urlOptions.highlight_before , (urlOptions.after > 0) ? urlOptions.after : null , (urlOptions.before > 0) ? urlOptions.before : null ); } else { NETDATA.globalChartUnderlay.clear(); } if (urlOptions.mode === 'print') { printPage(); } else { finalizePage(); } } function renderChartsAndMenu(data) { options.menus = {}; options.submenu_names = {}; var menus = options.menus; var charts = data.charts; var m, menu_key; for (var c in charts) { if (!charts.hasOwnProperty(c)) { continue; } var chart = charts[c]; enrichChartData(chart); m = chart.menu; // create the menu if (typeof menus[m] === 'undefined') { menus[m] = { menu_pattern: chart.menu_pattern, priority: chart.priority, submenus: {}, title: netdataDashboard.menuTitle(chart), icon: netdataDashboard.menuIcon(chart), info: netdataDashboard.menuInfo(chart), height: netdataDashboard.menuHeight(chart) * options.chartsHeight }; } else { if (typeof (menus[m].menu_pattern) === 'undefined') { menus[m].menu_pattern = chart.menu_pattern; } if (chart.priority < menus[m].priority) { menus[m].priority = chart.priority; } } menu_key = (typeof (menus[m].menu_pattern) !== 'undefined') ? menus[m].menu_pattern : m; // create the submenu if (typeof menus[m].submenus[chart.submenu] === 'undefined') { menus[m].submenus[chart.submenu] = { priority: chart.priority, charts: [], title: null, info: netdataDashboard.submenuInfo(menu_key, chart.submenu), height: netdataDashboard.submenuHeight(menu_key, chart.submenu, menus[m].height) }; } else { if (chart.priority < menus[m].submenus[chart.submenu].priority) { menus[m].submenus[chart.submenu].priority = chart.priority; } } // index the chart in the menu/submenu menus[m].submenus[chart.submenu].charts.push(chart); } // propagate the descriptive subname given to QoS // to all the other submenus with the same name for (var m in menus) { if (!menus.hasOwnProperty(m)) { continue; } for (var s in menus[m].submenus) { if (!menus[m].submenus.hasOwnProperty(s)) { continue; } // set the family using a name if (typeof options.submenu_names[s] !== 'undefined') { menus[m].submenus[s].title = s + ' (' + options.submenu_names[s] + ')'; } else { menu_key = (typeof (menus[m].menu_pattern) !== 'undefined') ? menus[m].menu_pattern : m; menus[m].submenus[s].title = netdataDashboard.submenuTitle(menu_key, s); } } } renderPage(menus, data); } // ---------------------------------------------------------------------------- function loadJs(url, callback) { $.ajax({ url: url.startsWith("http") ? url : transformWithOldSuffix(url), cache: true, dataType: "script", xhrFields: { withCredentials: true } // required for the cookie }) .fail(function () { alert('Cannot load required JS library: ' + url); }) .always(function () { if (typeof callback === 'function') { callback(); } }) } var clipboardLoaded = false; function loadClipboard(callback) { if (clipboardLoaded === false) { clipboardLoaded = true; loadJs('lib/clipboard-polyfill-be05dad.js', callback); } else { callback(); } } var bootstrapTableLoaded = false; function loadBootstrapTable(callback) { if (bootstrapTableLoaded === false) { bootstrapTableLoaded = true; loadJs('lib/bootstrap-table-1.11.0.min.js', function () { loadJs('lib/bootstrap-table-export-1.11.0.min.js', function () { loadJs('lib/tableExport-1.6.0.min.js', callback); }) }); } else { callback(); } } var bootstrapSliderLoaded = false; function loadBootstrapSlider(callback) { if (bootstrapSliderLoaded === false) { bootstrapSliderLoaded = true; loadJs('lib/bootstrap-slider-10.0.0.min.js', function () { NETDATA._loadCSS(transformWithOldSuffix("css/bootstrap-slider-10.0.0.min.css")); callback(); }); } else { callback(); } } var lzStringLoaded = false; function loadLzString(callback) { if (lzStringLoaded === false) { lzStringLoaded = true; loadJs('lib/lz-string-1.4.4.min.js', callback); } else { callback(); } } var pakoLoaded = false; function loadPako(callback) { if (pakoLoaded === false) { pakoLoaded = true; loadJs('lib/pako-1.0.6.min.js', callback); } else { callback(); } } // ---------------------------------------------------------------------------- function clipboardCopy(text) { clipboard.writeText(text); } function clipboardCopyBadgeEmbed(url) { clipboard.writeText(''); } // ---------------------------------------------------------------------------- function alarmsUpdateModal() { var active = '

Raised Alarms

'; var all = '

All Running Alarms

'; var footer = '
netdata badges refresh automatically. Their color indicates the state of the alarm:  red  is critical,  orange  is warning,  bright green  is ok,  light grey  is undefined (i.e. no data or no status),  black  is not initialized. You can copy and paste their URLs to embed them in any web page.
netdata can send notifications for these alarms. Check this configuration file for more information.'; loadClipboard(function () { }); NETDATA.alarms.get('all', function (data) { options.alarm_families = []; alarmsCallback(data); if (data === null) { document.getElementById('alarms_active').innerHTML = document.getElementById('alarms_all').innerHTML = document.getElementById('alarms_log').innerHTML = 'failed to load alarm data!'; return; } function alarmid4human(id) { if (id === 0) { return '-'; } return id.toString(); } function timestamp4human(timestamp, space) { if (timestamp === 0) { return '-'; } if (typeof space === 'undefined') { space = ' '; } var t = new Date(timestamp * 1000); var now = new Date(); if (t.toDateString() === now.toDateString()) { return t.toLocaleTimeString(); } return t.toLocaleDateString() + space + t.toLocaleTimeString(); } function alarm_lookup_explain(alarm, chart) { var dimensions = ' of all values '; if (chart.dimensions.length > 1) { dimensions = ' of the sum of all dimensions '; } if (typeof alarm.lookup_dimensions !== 'undefined') { var d = alarm.lookup_dimensions.replace(/|/g, ','); var x = d.split(','); if (x.length > 1) { dimensions = 'of the sum of dimensions ' + alarm.lookup_dimensions + ' '; } else { dimensions = 'of all values of dimension ' + alarm.lookup_dimensions + ' '; } } return '' + alarm.lookup_method + ' ' + dimensions + ', of chart ' + alarm.chart + '' + ', starting ' + NETDATA.seconds4human(alarm.lookup_after + alarm.lookup_before, { space: ' ' }) + ' and up to ' + NETDATA.seconds4human(alarm.lookup_before, { space: ' ' }) + '' + ((alarm.lookup_options) ? (', with options ' + alarm.lookup_options.replace(/ /g, ', ') + '') : '') + '.'; } function alarm_to_html(alarm, full) { var chart = options.data.charts[alarm.chart]; if (typeof (chart) === 'undefined') { chart = options.data.charts_by_name[alarm.chart]; if (typeof (chart) === 'undefined') { // this means the charts loaded are incomplete // probably netdata was restarted and more alarms // are now available. console.log('Cannot find chart ' + alarm.chart + ', you probably need to refresh the page.'); return ''; } } var has_alarm = (typeof alarm.warn !== 'undefined' || typeof alarm.crit !== 'undefined'); var badge_url = NETDATA.alarms.server + '/api/v1/badge.svg?chart=' + alarm.chart + '&alarm=' + alarm.name + '&refresh=auto'; var action_buttons = '
 
role: ' + alarm.recipient + '
 
' + '
' + '
' + '
'; var html = '
' + ''; return html; } function alarm_family_show(id) { var html = '
' + alarm.chart + '
 

 
' + alarm.info + '' + action_buttons + '
' + ((typeof alarm.warn !== 'undefined') ? ('') : '') + ((typeof alarm.crit !== 'undefined') ? ('') : ''); if (full === true) { var units = chart.units; if (units === '%') { units = '%'; } html += ((typeof alarm.lookup_after !== 'undefined') ? ('') : '') + ((typeof alarm.calc !== 'undefined') ? ('') : '') + ((chart.green !== null) ? ('') : '') + ((chart.red !== null) ? ('') : ''); } if (alarm.warn_repeat_every > 0) { html += ''; } if (alarm.crit_repeat_every > 0) { html += ''; } var delay = ''; if ((alarm.delay_up_duration > 0 || alarm.delay_down_duration > 0) && alarm.delay_multiplier !== 0 && alarm.delay_max_duration > 0) { if (alarm.delay_up_duration === alarm.delay_down_duration) { delay += '
hysteresis ' + NETDATA.seconds4human(alarm.delay_up_duration, { space: ' ', negative_suffix: '' }); } else { delay = '
hysteresis '; if (alarm.delay_up_duration > 0) { delay += 'on escalation ' + NETDATA.seconds4human(alarm.delay_up_duration, { space: ' ', negative_suffix: '' }) + ', '; } if (alarm.delay_down_duration > 0) { delay += 'on recovery ' + NETDATA.seconds4human(alarm.delay_down_duration, { space: ' ', negative_suffix: '' }) + ', '; } } if (alarm.delay_multiplier !== 1.0) { delay += 'multiplied by ' + alarm.delay_multiplier.toString() + ''; delay += ', up to ' + NETDATA.seconds4human(alarm.delay_max_duration, { space: ' ', negative_suffix: '' }) + ''; } delay += '
'; } html += '
' + ((has_alarm === true) ? ('') : '') + '' + '
warning when' + alarm.warn + '
critical when' + alarm.crit + '
db lookup' + alarm_lookup_explain(alarm, chart) + '
calculation' + alarm.calc + '
green threshold' + chart.green + ' ' + units + '
red threshold' + chart.red + ' ' + units + '
repeat warning' + NETDATA.seconds4human(alarm.warn_repeat_every) + '
repeat critical' + NETDATA.seconds4human(alarm.crit_repeat_every) + '
check every' + NETDATA.seconds4human(alarm.update_every, { space: ' ', negative_suffix: '' }) + '
execute' + alarm.exec + '' + delay + '
source' + alarm.source + '
'; var family = options.alarm_families[id]; var len = family.arr.length; while (len--) { var alarm = family.arr[len]; html += alarm_to_html(alarm, true); } html += '
'; $('#alarm_all_' + id.toString()).html(html); enableTooltipsAndPopovers(); } // find the proper family of each alarm var x, family, alarm; var count_active = 0; var count_all = 0; var families = {}; var families_sort = []; for (x in data.alarms) { if (!data.alarms.hasOwnProperty(x)) { continue; } alarm = data.alarms[x]; family = alarm.family; // find the chart var chart = options.data.charts[alarm.chart]; if (typeof chart === 'undefined') { chart = options.data.charts_by_name[alarm.chart]; } // not found - this should never happen! if (typeof chart === 'undefined') { console.log('WARNING: alarm ' + x + ' is linked to chart ' + alarm.chart + ', which is not found in the list of chart got from the server.'); chart = { priority: 9999999 }; } else if (typeof chart.menu !== 'undefined' && typeof chart.submenu !== 'undefined') // the family based on the chart { family = chart.menu + ' - ' + chart.submenu; } if (typeof families[family] === 'undefined') { families[family] = { name: family, arr: [], priority: chart.priority }; families_sort.push(families[family]); } if (chart.priority < families[family].priority) { families[family].priority = chart.priority; } families[family].arr.unshift(alarm); } // sort the families, like the dashboard menu does var families_sorted = families_sort.sort(function (a, b) { if (a.priority < b.priority) { return -1; } if (a.priority > b.priority) { return 1; } return naturalSortCompare(a.name, b.name); }); var i = 0; var fc = 0; var len = families_sorted.length; while (len--) { family = families_sorted[i++].name; var active_family_added = false; var expanded = 'true'; var collapsed = ''; var cin = 'in'; if (fc !== 0) { all += ""; expanded = 'false'; collapsed = 'class="collapsed"'; cin = ''; } all += '
'; options.alarm_families[fc] = families[family]; fc++; var arr = families[family].arr; var c = arr.length; while (c--) { alarm = arr[c]; if (alarm.status === 'WARNING' || alarm.status === 'CRITICAL') { if (!active_family_added) { active_family_added = true; active += '

' + family + '

'; } count_active++; active += alarm_to_html(alarm, true); } count_all++; } } active += ""; if (families_sorted.length > 0) { all += "
"; } all += ""; if (!count_active) { active += '

Everything is normal. No raised alarms.
'; } else { active += footer; } if (!count_all) { all += "

No alarms are running in this system.

"; } else { all += footer; } document.getElementById('alarms_active').innerHTML = active; document.getElementById('alarms_all').innerHTML = all; enableTooltipsAndPopovers(); if (families_sorted.length > 0) { alarm_family_show(0); } // register bootstrap events var $accordion = $('#alarms_all_accordion'); $accordion.on('show.bs.collapse', function (d) { var target = $(d.target); var id = $(target).data('alarm-id'); alarm_family_show(id); }); $accordion.on('hidden.bs.collapse', function (d) { var target = $(d.target); var id = $(target).data('alarm-id'); $('#alarm_all_' + id.toString()).html(''); }); document.getElementById('alarms_log').innerHTML = '

Alarm Log

'; loadBootstrapTable(function () { $('#alarms_log_table').bootstrapTable({ url: NETDATA.alarms.server + '/api/v1/alarm_log?all', cache: false, pagination: true, pageSize: 10, showPaginationSwitch: false, search: true, searchTimeOut: 300, searchAlign: 'left', showColumns: true, showExport: true, exportDataType: 'basic', exportOptions: { fileName: 'netdata_alarm_log' }, onClickRow: function (row, $element,field) { void (field); void ($element); let main_url; let common_url = "&host=" + encodeURIComponent(row['hostname']) + "&chart=" + encodeURIComponent(row['chart']) + "&family=" + encodeURIComponent(row['family']) + "&alarm=" + encodeURIComponent(row['name']) + "&alarm_unique_id=" + row['unique_id'] + "&alarm_id=" + row['alarm_id'] + "&alarm_event_id=" + row['alarm_event_id'] + "&alarm_when=" + row['when']; if (NETDATA.registry.isUsingGlobalRegistry() && NETDATA.registry.machine_guid != null) { main_url = "https://netdata.cloud/alarms/redirect?agentID=" + NETDATA.registry.machine_guid + common_url; } else { main_url = NETDATA.registry.server + "/goto-host-from-alarm.html?" + common_url ; } window.open(main_url,"_blank"); }, rowStyle: function (row, index) { void (index); switch (row.status) { case 'CRITICAL': return { classes: 'danger' }; break; case 'WARNING': return { classes: 'warning' }; break; case 'UNDEFINED': return { classes: 'info' }; break; case 'CLEAR': return { classes: 'success' }; break; } return {}; }, showFooter: false, showHeader: true, showRefresh: true, showToggle: false, sortable: true, silentSort: false, columns: [ { field: 'when', title: 'Event Date', valign: 'middle', titleTooltip: 'The date and time the even took place', formatter: function (value, row, index) { void (row); void (index); return timestamp4human(value, ' '); }, align: 'center', switchable: false, sortable: true }, { field: 'hostname', title: 'Host', valign: 'middle', titleTooltip: 'The host that generated this event', align: 'center', visible: false, sortable: true }, { field: 'unique_id', title: 'Unique ID', titleTooltip: 'The host unique ID for this event', formatter: function (value, row, index) { void (row); void (index); return alarmid4human(value); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'alarm_id', title: 'Alarm ID', titleTooltip: 'The ID of the alarm that generated this event', formatter: function (value, row, index) { void (row); void (index); return alarmid4human(value); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'alarm_event_id', title: 'Alarm Event ID', titleTooltip: 'The incremental ID of this event for the given alarm', formatter: function (value, row, index) { void (row); void (index); return alarmid4human(value); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'chart', title: 'Chart', titleTooltip: 'The chart the alarm is attached to', align: 'center', valign: 'middle', switchable: false, sortable: true }, { field: 'family', title: 'Family', titleTooltip: 'The family of the chart the alarm is attached to', align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'name', title: 'Alarm', titleTooltip: 'The alarm name that generated this event', formatter: function (value, row, index) { void (row); void (index); return value.toString().replace(/_/g, ' '); }, align: 'center', valign: 'middle', switchable: false, sortable: true }, { field: 'value_string', title: 'Friendly Value', titleTooltip: 'The value of the alarm, that triggered this event', align: 'right', valign: 'middle', sortable: true }, { field: 'old_value_string', title: 'Friendly Old Value', titleTooltip: 'The value of the alarm, just before this event', align: 'right', valign: 'middle', visible: false, sortable: true }, { field: 'old_value', title: 'Old Value', titleTooltip: 'The value of the alarm, just before this event', formatter: function (value, row, index) { void (row); void (index); return ((value !== null) ? Math.round(value * 100) / 100 : 'NaN').toString(); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'value', title: 'Value', titleTooltip: 'The value of the alarm, that triggered this event', formatter: function (value, row, index) { void (row); void (index); return ((value !== null) ? Math.round(value * 100) / 100 : 'NaN').toString(); }, align: 'right', valign: 'middle', visible: false, sortable: true }, { field: 'units', title: 'Units', titleTooltip: 'The units of the value of the alarm', align: 'left', valign: 'middle', visible: false, sortable: true }, { field: 'old_status', title: 'Old Status', titleTooltip: 'The status of the alarm, just before this event', align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'status', title: 'Status', titleTooltip: 'The status of the alarm, that was set due to this event', align: 'center', valign: 'middle', switchable: false, sortable: true }, { field: 'duration', title: 'Last Duration', titleTooltip: 'The duration the alarm was at its previous state, just before this event', formatter: function (value, row, index) { void (row); void (index); return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'non_clear_duration', title: 'Raised Duration', titleTooltip: 'The duration the alarm was raised, just before this event', formatter: function (value, row, index) { void (row); void (index); return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'recipient', title: 'Recipient', titleTooltip: 'The recipient of this event', align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'processed', title: 'Processed Status', titleTooltip: 'True when this event is processed', formatter: function (value, row, index) { void (row); void (index); if (value === true) { return 'DONE'; } else { return 'PENDING'; } }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'updated', title: 'Updated Status', titleTooltip: 'True when this event has been updated by another event', formatter: function (value, row, index) { void (row); void (index); if (value === true) { return 'UPDATED'; } else { return 'CURRENT'; } }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'updated_by_id', title: 'Updated By ID', titleTooltip: 'The unique ID of the event that obsoleted this one', formatter: function (value, row, index) { void (row); void (index); return alarmid4human(value); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'updates_id', title: 'Updates ID', titleTooltip: 'The unique ID of the event obsoleted because of this event', formatter: function (value, row, index) { void (row); void (index); return alarmid4human(value); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'exec', title: 'Script', titleTooltip: 'The script to handle the event notification', align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'exec_run', title: 'Script Run At', titleTooltip: 'The date and time the script has been ran', formatter: function (value, row, index) { void (row); void (index); return timestamp4human(value, ' '); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'exec_code', title: 'Script Return Value', titleTooltip: 'The return code of the script', formatter: function (value, row, index) { void (row); void (index); if (value === 0) { return 'OK (returned 0)'; } else { return 'FAILED (with code ' + value.toString() + ')'; } }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'delay', title: 'Script Delay', titleTooltip: 'The hysteresis of the notification', formatter: function (value, row, index) { void (row); void (index); return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'delay_up_to_timestamp', title: 'Script Delay Run At', titleTooltip: 'The date and time the script should be run, after hysteresis', formatter: function (value, row, index) { void (row); void (index); return timestamp4human(value, ' '); }, align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'info', title: 'Description', titleTooltip: 'A short description of the alarm', align: 'center', valign: 'middle', visible: false, sortable: true }, { field: 'source', title: 'Alarm Source', titleTooltip: 'The source of configuration of the alarm', align: 'center', valign: 'middle', visible: false, sortable: true } ] }); // console.log($('#alarms_log_table').bootstrapTable('getOptions')); }); }); } function alarmsCallback(data) { var count = 0, x; for (x in data.alarms) { if (!data.alarms.hasOwnProperty(x)) { continue; } var alarm = data.alarms[x]; if (alarm.status === 'WARNING' || alarm.status === 'CRITICAL') { count++; } } if (count > 0) { document.getElementById('alarms_count_badge').innerHTML = count.toString(); } else { document.getElementById('alarms_count_badge').innerHTML = ''; } } function initializeDynamicDashboardWithData(data) { if (data !== null) { options.hostname = data.hostname; options.data = data; options.version = data.version; options.release_channel = data.release_channel; netdataDashboard.os = data.os; if (typeof data.hosts !== 'undefined') { options.hosts = data.hosts; } // update the dashboard hostname document.getElementById('hostname').innerHTML = '' + options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString() + '  '; document.getElementById('hostname').href = NETDATA.serverDefault; document.getElementById('netdataVersion').innerHTML = options.version; if (netdataSnapshotData !== null) { $('#alarmsButton').hide(); $('#updateButton').hide(); // $('#loadButton').hide(); $('#saveButton').hide(); $('#printButton').hide(); } // update the dashboard title document.title = options.hostname + ' netdata dashboard'; // close the splash screen $("#loadOverlay").css("display", "none"); // create a chart_by_name index data.charts_by_name = {}; var charts = data.charts; var x; for (x in charts) { if (!charts.hasOwnProperty(x)) { continue; } var chart = charts[x]; data.charts_by_name[chart.name] = chart; } // render all charts renderChartsAndMenu(data); // Ensure MyNetdata menu is rendered with latest host info #5370 renderMyNetdataMenu(isSignedIn() ? cloudAgents : registryAgents); } } // an object to keep initialization configuration // needed due to the async nature of the XSS modal var initializeConfig = { url: null, custom_info: true, }; function loadCustomDashboardInfo(url, callback) { loadJs(url, function () { $.extend(true, netdataDashboard, customDashboard); callback(); }); } function initializeChartsAndCustomInfo() { NETDATA.alarms.callback = alarmsCallback; // download all the charts the server knows NETDATA.chartRegistry.downloadAll(initializeConfig.url, function (data) { if (data !== null) { if (initializeConfig.custom_info === true && typeof data.custom_info !== 'undefined' && data.custom_info !== "" && netdataSnapshotData === null) { //console.log('loading custom dashboard decorations from server ' + initializeConfig.url); loadCustomDashboardInfo(NETDATA.serverDefault + data.custom_info, function () { initializeDynamicDashboardWithData(data); }); } else { //console.log('not loading custom dashboard decorations from server ' + initializeConfig.url); initializeDynamicDashboardWithData(data); } } }); } function xssModalDisableXss() { //console.log('disabling xss checks'); NETDATA.xss.enabled = false; NETDATA.xss.enabled_for_data = false; initializeConfig.custom_info = true; initializeChartsAndCustomInfo(); return false; } function xssModalKeepXss() { //console.log('keeping xss checks'); NETDATA.xss.enabled = true; NETDATA.xss.enabled_for_data = true; initializeConfig.custom_info = false; initializeChartsAndCustomInfo(); return false; } function initializeDynamicDashboard(netdata_url) { if (typeof netdata_url === 'undefined' || netdata_url === null) { netdata_url = NETDATA.serverDefault; } initializeConfig.url = netdata_url; // initialize clickable alarms NETDATA.alarms.chart_div_offset = -50; NETDATA.alarms.chart_div_id_prefix = 'chart_'; NETDATA.alarms.chart_div_animation_duration = 0; NETDATA.pause(function () { if (typeof netdataCheckXSS !== 'undefined' && netdataCheckXSS === true) { //$("#loadOverlay").css("display","none"); document.getElementById('netdataXssModalServer').innerText = initializeConfig.url; $('#xssModal').modal('show'); } else { initializeChartsAndCustomInfo(); } }); } // ---------------------------------------------------------------------------- function versionLog(msg) { document.getElementById('versionCheckLog').innerHTML = msg; } // New way of checking for updates, based only on versions function versionsMatch(v1, v2) { if (v1 == v2) { return true; } else { let s1 = v1.split('.'); let s2 = v2.split('.'); // Check major version let n1 = parseInt(s1[0].substring(1, 2), 10); let n2 = parseInt(s2[0].substring(1, 2), 10); if (n1 < n2) return false; else if (n1 > n2) return true; // Check minor version n1 = parseInt(s1[1], 10); n2 = parseInt(s2[1], 10); if (n1 < n2) return false; else if (n1 > n2) return true; // Split patch: format could be e.g. 0-22-nightly s1 = s1[2].split('-'); s2 = s2[2].split('-'); n1 = parseInt(s1[0], 10); n2 = parseInt(s2[0], 10); if (n1 < n2) return false; else if (n1 > n2) return true; n1 = (s1.length > 1) ? parseInt(s1[1], 10) : 0; n2 = (s2.length > 1) ? parseInt(s2[1], 10) : 0; if (n1 < n2) return false; else return true; } } function getGithubLatestVersion(callback) { versionLog('Downloading latest version id from github...'); $.ajax({ url: 'https://api.github.com/repos/netdata/netdata/releases/latest', async: true, cache: false }) .done(function (data) { data = data.tag_name.replace(/(\r\n|\n|\r| |\t)/gm, ""); versionLog('Latest stable version from github is ' + data); callback(data); }) .fail(function () { versionLog('Failed to download the latest stable version id from github!'); callback(null); }); } function getGCSLatestVersion(callback) { versionLog('Downloading latest version id from GCS...'); $.ajax({ url: "https://www.googleapis.com/storage/v1/b/netdata-nightlies/o/latest-version.txt", async: true, cache: false }) .done(function (response) { $.ajax({ url: response.mediaLink, async: true, cache: false }) .done(function (data) { data = data.replace(/(\r\n|\n|\r| |\t)/gm, ""); versionLog('Latest nightly version from GCS is ' + data); callback(data); }) .fail(function (xhr, textStatus, errorThrown) { versionLog('Failed to download the latest nightly version id from GCS!'); callback(null); }); }) .fail(function (xhr, textStatus, errorThrown) { versionLog('Failed to download the latest nightly version from GCS!'); callback(null); }); } function checkForUpdateByVersion(force, callback) { if (options.release_channel === 'stable') { getGithubLatestVersion(function (sha2) { callback(options.version, sha2); }); } else { getGCSLatestVersion(function (sha2) { callback(options.version, sha2); }); } return null; } function notifyForUpdate(force) { versionLog('

checking for updates...

'); var now = Date.now(); if (typeof force === 'undefined' || force !== true) { var last = loadLocalStorage('last_update_check'); if (typeof last === 'string') { last = parseInt(last); } else { last = 0; } if (now - last < 3600000 * 8) { // no need to check it - too soon return; } } checkForUpdateByVersion(force, function (sha1, sha2) { var save = false; if (sha1 === null) { save = false; versionLog('

Failed to get your netdata version!

You can always get the latest netdata from its github page.

'); } else if (sha2 === null) { save = false; versionLog('

Failed to get the latest netdata version.

You can always get the latest netdata from its github page.

'); } else if (versionsMatch(sha1, sha2)) { save = true; versionLog('

You already have the latest netdata!

No update yet?
We probably need some motivation to keep going on!

If you haven\'t already, give netdata a at its github page.

'); } else { save = true; var compare = 'https://learn.netdata.cloud/docs/agent/changelog/'; versionLog('

New version of netdata available!

Latest version: ' + sha2 + '

Click here for the changes log and
click here for directions on updating your netdata installation.

We suggest to review the changes log for new features you may be interested, or important bug fixes you may need.
Keeping your netdata updated is generally a good idea.

'); document.getElementById('update_badge').innerHTML = '!'; } if (save) { saveLocalStorage('last_update_check', now.toString()); } }); } // ---------------------------------------------------------------------------- // printing dashboards function showPageFooter() { document.getElementById('footer').style.display = 'block'; } function printPreflight() { var url = document.location.origin.toString() + document.location.pathname.toString() + document.location.search.toString() + urlOptions.genHash() + ';mode=print'; var width = 990; var height = screen.height * 90 / 100; //console.log(url); //console.log(document.location); window.open(url, '', 'width=' + width.toString() + ',height=' + height.toString() + ',menubar=no,toolbar=no,personalbar=no,location=no,resizable=no,scrollbars=yes,status=no,chrome=yes,centerscreen=yes,attention=yes,dialog=yes'); $('#printPreflightModal').modal('hide'); } function printPage() { var print_is_rendering = true; $('#printModal').on('hide.bs.modal', function (e) { if (print_is_rendering === true) { e.preventDefault(); return false; } return true; }); $('#printModal').on('show.bs.modal', function () { var print_options = { stop_updates_when_focus_is_lost: false, update_only_visible: false, sync_selection: false, eliminate_zero_dimensions: false, pan_and_zoom_data_padding: false, show_help: false, legend_toolbox: false, resize_charts: false, pixels_per_point: 1 }; var x; for (x in print_options) { if (print_options.hasOwnProperty(x)) { NETDATA.options.current[x] = print_options[x]; } } NETDATA.parseDom(); showPageFooter(); NETDATA.globalSelectionSync.stop(); NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], urlOptions.after, urlOptions.before); // NETDATA.onresize(); var el = document.getElementById('printModalProgressBar'); var eltxt = document.getElementById('printModalProgressBarText'); function update_chart(idx) { var state = NETDATA.options.targets[--idx]; var pcent = (NETDATA.options.targets.length - idx) * 100 / NETDATA.options.targets.length; $(el).css('width', pcent + '%').attr('aria-valuenow', pcent); eltxt.innerText = Math.round(pcent).toString() + '%, ' + state.id; setTimeout(function () { state.updateChart(function () { NETDATA.options.targets[idx].resizeForPrint(); if (idx > 0) { update_chart(idx); } else { print_is_rendering = false; $('#printModal').modal('hide'); window.print(); window.close(); } }) }, 0); } print_is_rendering = true; update_chart(NETDATA.options.targets.length); }); $('#printModal').modal('show'); } // -------------------------------------------------------------------- function jsonStringifyFn(obj) { return JSON.stringify(obj, function (key, value) { return (typeof value === 'function') ? value.toString() : value; }); } function jsonParseFn(str) { return JSON.parse(str, function (key, value) { if (typeof value != 'string') { return value; } return (value.substring(0, 8) == 'function') ? eval('(' + value + ')') : value; }); } // -------------------------------------------------------------------- var snapshotOptions = { bytes_per_chart: 2048, compressionDefault: 'pako.deflate.base64', compressions: { 'none': { bytes_per_point_memory: 5.2, bytes_per_point_disk: 5.6, compress: function (s) { return s; }, compressed_length: function (s) { return s.length; }, uncompress: function (s) { return s; } }, 'pako.deflate.base64': { bytes_per_point_memory: 1.8, bytes_per_point_disk: 1.9, compress: function (s) { return btoa(pako.deflate(s, { to: 'string' })); }, compressed_length: function (s) { return s.length; }, uncompress: function (s) { return pako.inflate(atob(s), { to: 'string' }); } }, 'pako.deflate': { bytes_per_point_memory: 1.4, bytes_per_point_disk: 3.2, compress: function (s) { return pako.deflate(s, { to: 'string' }); }, compressed_length: function (s) { return s.length; }, uncompress: function (s) { return pako.inflate(s, { to: 'string' }); } }, 'lzstring.utf16': { bytes_per_point_memory: 1.7, bytes_per_point_disk: 2.6, compress: function (s) { return LZString.compressToUTF16(s); }, compressed_length: function (s) { return s.length * 2; }, uncompress: function (s) { return LZString.decompressFromUTF16(s); } }, 'lzstring.base64': { bytes_per_point_memory: 2.1, bytes_per_point_disk: 2.3, compress: function (s) { return LZString.compressToBase64(s); }, compressed_length: function (s) { return s.length; }, uncompress: function (s) { return LZString.decompressFromBase64(s); } }, 'lzstring.uri': { bytes_per_point_memory: 2.1, bytes_per_point_disk: 2.3, compress: function (s) { return LZString.compressToEncodedURIComponent(s); }, compressed_length: function (s) { return s.length; }, uncompress: function (s) { return LZString.decompressFromEncodedURIComponent(s); } } } }; // -------------------------------------------------------------------- // loading snapshots function loadSnapshotModalLog(priority, msg) { document.getElementById('loadSnapshotStatus').className = "alert alert-" + priority; document.getElementById('loadSnapshotStatus').innerHTML = msg; } var tmpSnapshotData = null; function loadSnapshot() { $('#loadSnapshotImport').addClass('disabled'); if (tmpSnapshotData === null) { loadSnapshotPreflightEmpty(); loadSnapshotModalLog('danger', 'no data have been loaded'); return; } loadPako(function () { loadLzString(function () { loadSnapshotModalLog('info', 'Please wait, activating snapshot...'); $('#loadSnapshotModal').modal('hide'); netdataShowAlarms = false; netdataRegistry = false; netdataServer = tmpSnapshotData.server; NETDATA.serverDefault = netdataServer; document.getElementById('charts_div').innerHTML = ''; document.getElementById('sidebar').innerHTML = ''; NETDATA.globalReset(); if (typeof tmpSnapshotData.hash !== 'undefined') { urlOptions.hash = tmpSnapshotData.hash; } else { urlOptions.hash = '#'; } if (typeof tmpSnapshotData.info !== 'undefined') { var info = jsonParseFn(tmpSnapshotData.info); if (typeof info.menu !== 'undefined') { netdataDashboard.menu = info.menu; } if (typeof info.submenu !== 'undefined') { netdataDashboard.submenu = info.submenu; } if (typeof info.context !== 'undefined') { netdataDashboard.context = info.context; } } if (typeof tmpSnapshotData.compression !== 'string') { tmpSnapshotData.compression = 'none'; } if (typeof snapshotOptions.compressions[tmpSnapshotData.compression] === 'undefined') { alert('unknown compression method: ' + tmpSnapshotData.compression); tmpSnapshotData.compression = 'none'; } tmpSnapshotData.uncompress = snapshotOptions.compressions[tmpSnapshotData.compression].uncompress; netdataSnapshotData = tmpSnapshotData; urlOptions.after = tmpSnapshotData.after_ms; urlOptions.before = tmpSnapshotData.before_ms; if (typeof tmpSnapshotData.highlight_after_ms !== 'undefined' && tmpSnapshotData.highlight_after_ms !== null && tmpSnapshotData.highlight_after_ms > 0 && typeof tmpSnapshotData.highlight_before_ms !== 'undefined' && tmpSnapshotData.highlight_before_ms !== null && tmpSnapshotData.highlight_before_ms > 0 ) { urlOptions.highlight_after = tmpSnapshotData.highlight_after_ms; urlOptions.highlight_before = tmpSnapshotData.highlight_before_ms; urlOptions.highlight = true; } else { urlOptions.highlight_after = 0; urlOptions.highlight_before = 0; urlOptions.highlight = false; } netdataCheckXSS = false; // disable the modal - this does not affect XSS checks, since dashboard.js is already loaded NETDATA.xss.enabled = true; // we should not do any remote requests, but if we do, check them NETDATA.xss.enabled_for_data = true; // check also snapshot data - that have been excluded from the initial check, due to compression loadSnapshotPreflightEmpty(); initializeDynamicDashboard(); }); }); }; function loadSnapshotPreflightFile(file) { var filename = NETDATA.xss.string(file.name); var fr = new FileReader(); fr.onload = function (e) { document.getElementById('loadSnapshotFilename').innerHTML = filename; var result = null; try { result = NETDATA.xss.checkAlways('snapshot', JSON.parse(e.target.result), /^(snapshot\.info|snapshot\.data)$/); //console.log(result); var date_after = new Date(result.after_ms); var date_before = new Date(result.before_ms); if (typeof result.charts_ok === 'undefined') { result.charts_ok = 'unknown'; } if (typeof result.charts_failed === 'undefined') { result.charts_failed = 0; } if (typeof result.compression === 'undefined') { result.compression = 'none'; } if (typeof result.data_size === 'undefined') { result.data_size = 0; } document.getElementById('loadSnapshotFilename').innerHTML = '' + filename + ''; document.getElementById('loadSnapshotHostname').innerHTML = '' + result.hostname + ', netdata version: ' + result.netdata_version.toString() + ''; document.getElementById('loadSnapshotURL').innerHTML = result.url; document.getElementById('loadSnapshotCharts').innerHTML = result.charts.charts_count.toString() + ' charts, ' + result.charts.dimensions_count.toString() + ' dimensions, ' + result.data_points.toString() + ' points per dimension, ' + Math.round(result.duration_ms / result.data_points).toString() + ' ms per point'; document.getElementById('loadSnapshotInfo').innerHTML = 'version: ' + result.snapshot_version.toString() + ', includes ' + result.charts_ok.toString() + ' unique chart data queries ' + ((result.charts_failed > 0) ? ('' + result.charts_failed.toString() + ' failed') : '').toString() + ', compressed with ' + result.compression.toString() + ', data size ' + (Math.round(result.data_size * 100 / 1024 / 1024) / 100).toString() + ' MB'; document.getElementById('loadSnapshotTimeRange').innerHTML = '' + NETDATA.dateTime.localeDateString(date_after) + ' ' + NETDATA.dateTime.localeTimeString(date_after) + ' to ' + NETDATA.dateTime.localeDateString(date_before) + ' ' + NETDATA.dateTime.localeTimeString(date_before) + ''; document.getElementById('loadSnapshotComments').innerHTML = ((result.comments) ? result.comments : '').toString(); loadSnapshotModalLog('success', 'File loaded, click Import to render it!'); $('#loadSnapshotImport').removeClass('disabled'); tmpSnapshotData = result; } catch (e) { console.log(e); document.getElementById('loadSnapshotStatus').className = "alert alert-danger"; document.getElementById('loadSnapshotStatus').innerHTML = "Failed to parse this file!"; $('#loadSnapshotImport').addClass('disabled'); } } //console.log(file); fr.readAsText(file); }; function loadSnapshotPreflightEmpty() { document.getElementById('loadSnapshotFilename').innerHTML = ''; document.getElementById('loadSnapshotHostname').innerHTML = ''; document.getElementById('loadSnapshotURL').innerHTML = ''; document.getElementById('loadSnapshotCharts').innerHTML = ''; document.getElementById('loadSnapshotInfo').innerHTML = ''; document.getElementById('loadSnapshotTimeRange').innerHTML = ''; document.getElementById('loadSnapshotComments').innerHTML = ''; loadSnapshotModalLog('success', 'Browse for a snapshot file (or drag it and drop it here), then click Import to render it.'); $('#loadSnapshotImport').addClass('disabled'); }; var loadSnapshotDragAndDropInitialized = false; function loadSnapshotDragAndDropSetup() { if (loadSnapshotDragAndDropInitialized === false) { loadSnapshotDragAndDropInitialized = true; $('#loadSnapshotDragAndDrop') .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) { e.preventDefault(); e.stopPropagation(); }) .on('drop', function (e) { if (e.originalEvent.dataTransfer.files.length) { loadSnapshotPreflightFile(e.originalEvent.dataTransfer.files.item(0)); } else { loadSnapshotPreflightEmpty(); loadSnapshotModalLog('danger', 'No file selected'); } }); } }; function loadSnapshotPreflight() { var files = document.getElementById('loadSnapshotSelectFiles').files; if (files.length <= 0) { loadSnapshotPreflightEmpty(); loadSnapshotModalLog('danger', 'No file selected'); return; } loadSnapshotModalLog('info', 'Loading file...'); loadSnapshotPreflightFile(files.item(0)); } // -------------------------------------------------------------------- // saving snapshots var saveSnapshotStop = false; function saveSnapshotCancel() { saveSnapshotStop = true; } var saveSnapshotModalInitialized = false; function saveSnapshotModalSetup() { if (saveSnapshotModalInitialized === false) { saveSnapshotModalInitialized = true; $('#saveSnapshotModal') .on('hide.bs.modal', saveSnapshotCancel) .on('show.bs.modal', saveSnapshotModalInit) .on('shown.bs.modal', function () { $('#saveSnapshotResolutionSlider').find(".slider-handle:first").attr("tabindex", 1); document.getElementById('saveSnapshotComments').focus(); }); } }; function saveSnapshotModalLog(priority, msg) { document.getElementById('saveSnapshotStatus').className = "alert alert-" + priority; document.getElementById('saveSnapshotStatus').innerHTML = msg; } function saveSnapshotModalShowExpectedSize() { var points = Math.round(saveSnapshotViewDuration / saveSnapshotSelectedSecondsPerPoint); var priority = 'info'; var msg = 'A moderate snapshot.'; var sizemb = Math.round( (options.data.charts_count * snapshotOptions.bytes_per_chart + options.data.dimensions_count * points * snapshotOptions.compressions[saveSnapshotCompression].bytes_per_point_disk) * 10 / 1024 / 1024) / 10; var memmb = Math.round( (options.data.charts_count * snapshotOptions.bytes_per_chart + options.data.dimensions_count * points * snapshotOptions.compressions[saveSnapshotCompression].bytes_per_point_memory) * 10 / 1024 / 1024) / 10; if (sizemb < 10) { priority = 'success'; msg = 'A nice small snapshot!'; } if (sizemb > 50) { priority = 'warning'; msg = 'Will stress your browser...'; } if (sizemb > 100) { priority = 'danger'; msg = 'Hm... good luck...'; } saveSnapshotModalLog(priority, 'The snapshot will have ' + points.toString() + ' points per dimension. Expected size on disk ' + sizemb + ' MB, at browser memory ' + memmb + ' MB.
' + msg); } var saveSnapshotCompression = snapshotOptions.compressionDefault; function saveSnapshotSetCompression(name) { saveSnapshotCompression = name; document.getElementById('saveSnapshotCompressionName').innerHTML = saveSnapshotCompression; saveSnapshotModalShowExpectedSize(); } var saveSnapshotSlider = null; var saveSnapshotSelectedSecondsPerPoint = 1; var saveSnapshotViewDuration = 1; function saveSnapshotModalInit() { $('#saveSnapshotModalProgressSection').hide(); $('#saveSnapshotResolutionRadio').show(); saveSnapshotModalLog('info', 'Select resolution and click Save'); $('#saveSnapshotExport').removeClass('disabled'); loadBootstrapSlider(function () { saveSnapshotViewDuration = options.duration; var start_ms = Math.round(Date.now() - saveSnapshotViewDuration * 1000); if (NETDATA.globalPanAndZoom.isActive() === true) { saveSnapshotViewDuration = Math.round((NETDATA.globalPanAndZoom.force_before_ms - NETDATA.globalPanAndZoom.force_after_ms) / 1000); start_ms = NETDATA.globalPanAndZoom.force_after_ms; } var start_date = new Date(start_ms); var yyyymmddhhssmm = start_date.getFullYear() + NETDATA.zeropad(start_date.getMonth() + 1) + NETDATA.zeropad(start_date.getDate()) + '-' + NETDATA.zeropad(start_date.getHours()) + NETDATA.zeropad(start_date.getMinutes()) + NETDATA.zeropad(start_date.getSeconds()); document.getElementById('saveSnapshotFilename').value = 'netdata-' + options.hostname.toString() + '-' + yyyymmddhhssmm.toString() + '-' + saveSnapshotViewDuration.toString() + '.snapshot'; saveSnapshotSetCompression(saveSnapshotCompression); var min = options.update_every; var max = Math.round(saveSnapshotViewDuration / 100); if (NETDATA.globalPanAndZoom.isActive() === false) { max = Math.round(saveSnapshotViewDuration / 50); } var view = Math.round(saveSnapshotViewDuration / Math.round($(document.getElementById('charts_div')).width() / 2)); // console.log('view duration: ' + saveSnapshotViewDuration + ', min: ' + min + ', max: ' + max + ', view: ' + view); if (max < 10) { max = 10; } if (max < min) { max = min; } if (view < min) { view = min; } if (view > max) { view = max; } if (saveSnapshotSlider !== null) { saveSnapshotSlider.destroy(); } saveSnapshotSlider = new Slider('#saveSnapshotResolutionSlider', { ticks: [min, view, max], min: min, max: max, step: options.update_every, value: view, scale: (max > 100) ? 'logarithmic' : 'linear', tooltip: 'always', formatter: function (value) { if (value < 1) { value = 1; } if (value < options.data.update_every) { value = options.data.update_every; } saveSnapshotSelectedSecondsPerPoint = value; saveSnapshotModalShowExpectedSize(); var seconds = ' seconds '; if (value === 1) { seconds = ' second '; } return value + seconds + 'per point' + ((value === options.data.update_every) ? ', server default' : '').toString(); } }); }); } function saveSnapshot() { loadPako(function () { loadLzString(function () { saveSnapshotStop = false; $('#saveSnapshotModalProgressSection').show(); $('#saveSnapshotResolutionRadio').hide(); $('#saveSnapshotExport').addClass('disabled'); var filename = document.getElementById('saveSnapshotFilename').value; // console.log(filename); saveSnapshotModalLog('info', 'Generating snapshot as ' + filename.toString() + ''); var save_options = { stop_updates_when_focus_is_lost: false, update_only_visible: false, sync_selection: false, eliminate_zero_dimensions: true, pan_and_zoom_data_padding: false, show_help: false, legend_toolbox: false, resize_charts: false, pixels_per_point: 1 }; var backedup_options = {}; var x; for (x in save_options) { if (save_options.hasOwnProperty(x)) { backedup_options[x] = NETDATA.options.current[x]; NETDATA.options.current[x] = save_options[x]; } } var el = document.getElementById('saveSnapshotModalProgressBar'); var eltxt = document.getElementById('saveSnapshotModalProgressBarText'); options.data.charts_by_name = null; var saveData = { hostname: options.hostname, server: NETDATA.serverDefault, netdata_version: options.data.version, snapshot_version: 1, after_ms: Date.now() - options.duration * 1000, before_ms: Date.now(), highlight_after_ms: urlOptions.highlight_after, highlight_before_ms: urlOptions.highlight_before, duration_ms: options.duration * 1000, update_every_ms: options.update_every * 1000, data_points: 0, url: ((urlOptions.server !== null) ? urlOptions.server : document.location.origin.toString() + document.location.pathname.toString() + document.location.search.toString()).toString(), comments: document.getElementById('saveSnapshotComments').value.toString(), hash: urlOptions.hash, charts: options.data, info: jsonStringifyFn({ menu: netdataDashboard.menu, submenu: netdataDashboard.submenu, context: netdataDashboard.context }), charts_ok: 0, charts_failed: 0, compression: saveSnapshotCompression, data_size: 0, data: {} }; if (typeof snapshotOptions.compressions[saveData.compression] === 'undefined') { alert('unknown compression method: ' + saveData.compression); saveData.compression = 'none'; } var compress = snapshotOptions.compressions[saveData.compression].compress; var compressed_length = snapshotOptions.compressions[saveData.compression].compressed_length; function pack_api1_v1_chart_data(state) { if (state.library_name === null || state.data === null) { return; } var data = state.data; state.data = null; data.state = null; var str = JSON.stringify(data); if (typeof str === 'string') { var cstr = compress(str); saveData.data[state.chartDataUniqueID()] = cstr; return compressed_length(cstr); } else { return 0; } } var clearPanAndZoom = false; if (NETDATA.globalPanAndZoom.isActive() === false) { NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], saveData.after_ms, saveData.before_ms); clearPanAndZoom = true; } saveData.after_ms = NETDATA.globalPanAndZoom.force_after_ms; saveData.before_ms = NETDATA.globalPanAndZoom.force_before_ms; saveData.duration_ms = saveData.before_ms - saveData.after_ms; saveData.data_points = Math.round((saveData.before_ms - saveData.after_ms) / (saveSnapshotSelectedSecondsPerPoint * 1000)); saveSnapshotModalLog('info', 'Generating snapshot with ' + saveData.data_points.toString() + ' data points per dimension...'); var charts_count = 0; var charts_ok = 0; var charts_failed = 0; function saveSnapshotRestore() { $('#saveSnapshotModal').modal('hide'); // restore the options var x; for (x in backedup_options) { if (backedup_options.hasOwnProperty(x)) { NETDATA.options.current[x] = backedup_options[x]; } } $(el).css('width', '0%').attr('aria-valuenow', 0); eltxt.innerText = '0%'; if (clearPanAndZoom) { NETDATA.globalPanAndZoom.clearMaster(); } NETDATA.options.force_data_points = 0; NETDATA.options.fake_chart_rendering = false; NETDATA.onscroll_updater_enabled = true; NETDATA.onresize(); NETDATA.unpause(); $('#saveSnapshotExport').removeClass('disabled'); } NETDATA.globalSelectionSync.stop(); NETDATA.options.force_data_points = saveData.data_points; NETDATA.options.fake_chart_rendering = true; NETDATA.onscroll_updater_enabled = false; NETDATA.abortAllRefreshes(); var size = 0; var info = ' Resolution: ' + saveSnapshotSelectedSecondsPerPoint.toString() + ((saveSnapshotSelectedSecondsPerPoint === 1) ? ' second ' : ' seconds ').toString() + 'per point.'; function update_chart(idx) { if (saveSnapshotStop === true) { saveSnapshotModalLog('info', 'Cancelled!'); saveSnapshotRestore(); return; } var state = NETDATA.options.targets[--idx]; var pcent = (NETDATA.options.targets.length - idx) * 100 / NETDATA.options.targets.length; $(el).css('width', pcent + '%').attr('aria-valuenow', pcent); eltxt.innerText = Math.round(pcent).toString() + '%, ' + state.id; setTimeout(function () { charts_count++; state.isVisible(true); state.current.force_after_ms = saveData.after_ms; state.current.force_before_ms = saveData.before_ms; state.updateChart(function (status, reason) { state.current.force_after_ms = null; state.current.force_before_ms = null; if (status === true) { charts_ok++; // state.log('ok'); size += pack_api1_v1_chart_data(state); } else { charts_failed++; state.log('failed to be updated: ' + reason); } saveSnapshotModalLog((charts_failed) ? 'danger' : 'info', 'Generated snapshot data size ' + (Math.round(size * 100 / 1024 / 1024) / 100).toString() + ' MB. ' + ((charts_failed) ? (charts_failed.toString() + ' charts have failed to be downloaded') : '').toString() + info); if (idx > 0) { update_chart(idx); } else { saveData.charts_ok = charts_ok; saveData.charts_failed = charts_failed; saveData.data_size = size; // console.log(saveData.compression + ': ' + (size / (options.data.dimensions_count * Math.round(saveSnapshotViewDuration / saveSnapshotSelectedSecondsPerPoint))).toString()); // save it // console.log(saveData); saveObjectToClient(saveData, filename); if (charts_failed > 0) { alert(charts_failed.toString() + ' failed to be downloaded'); } saveSnapshotRestore(); saveData = null; } }) }, 0); } update_chart(NETDATA.options.targets.length); }); }); } // -------------------------------------------------------------------- // activate netdata on the page function dashboardSettingsSetup() { var update_options_modal = function () { // console.log('update_options_modal'); var sync_option = function (option) { var self = $('#' + option); if (self.prop('checked') !== NETDATA.getOption(option)) { // console.log('switching ' + option.toString()); self.bootstrapToggle(NETDATA.getOption(option) ? 'on' : 'off'); } }; var theme_sync_option = function (option) { var self = $('#' + option); self.bootstrapToggle(netdataTheme === 'slate' ? 'on' : 'off'); }; var units_sync_option = function (option) { var self = $('#' + option); if (self.prop('checked') !== (NETDATA.getOption('units') === 'auto')) { self.bootstrapToggle(NETDATA.getOption('units') === 'auto' ? 'on' : 'off'); } if (self.prop('checked') === true) { $('#settingsLocaleTempRow').show(); $('#settingsLocaleTimeRow').show(); } else { $('#settingsLocaleTempRow').hide(); $('#settingsLocaleTimeRow').hide(); } }; var temp_sync_option = function (option) { var self = $('#' + option); if (self.prop('checked') !== (NETDATA.getOption('temperature') === 'celsius')) { self.bootstrapToggle(NETDATA.getOption('temperature') === 'celsius' ? 'on' : 'off'); } }; var timezone_sync_option = function (option) { var self = $('#' + option); document.getElementById('browser_timezone').innerText = NETDATA.options.browser_timezone; document.getElementById('server_timezone').innerText = NETDATA.options.server_timezone; document.getElementById('current_timezone').innerText = (NETDATA.options.current.timezone === 'default') ? 'unset, using browser default' : NETDATA.options.current.timezone; if (self.prop('checked') === NETDATA.dateTime.using_timezone) { self.bootstrapToggle(NETDATA.dateTime.using_timezone ? 'off' : 'on'); } }; sync_option('eliminate_zero_dimensions'); sync_option('destroy_on_hide'); sync_option('async_on_scroll'); sync_option('parallel_refresher'); sync_option('concurrent_refreshes'); sync_option('sync_selection'); sync_option('sync_pan_and_zoom'); sync_option('stop_updates_when_focus_is_lost'); sync_option('smooth_plot'); sync_option('pan_and_zoom_data_padding'); sync_option('show_help'); sync_option('seconds_as_time'); theme_sync_option('netdata_theme_control'); units_sync_option('units_conversion'); temp_sync_option('units_temp'); timezone_sync_option('local_timezone'); if (NETDATA.getOption('parallel_refresher') === false) { $('#concurrent_refreshes_row').hide(); } else { $('#concurrent_refreshes_row').show(); } }; NETDATA.setOption('setOptionCallback', update_options_modal); // handle options changes $('#eliminate_zero_dimensions').change(function () { NETDATA.setOption('eliminate_zero_dimensions', $(this).prop('checked')); }); $('#destroy_on_hide').change(function () { NETDATA.setOption('destroy_on_hide', $(this).prop('checked')); }); $('#async_on_scroll').change(function () { NETDATA.setOption('async_on_scroll', $(this).prop('checked')); }); $('#parallel_refresher').change(function () { NETDATA.setOption('parallel_refresher', $(this).prop('checked')); }); $('#concurrent_refreshes').change(function () { NETDATA.setOption('concurrent_refreshes', $(this).prop('checked')); }); $('#sync_selection').change(function () { NETDATA.setOption('sync_selection', $(this).prop('checked')); }); $('#sync_pan_and_zoom').change(function () { NETDATA.setOption('sync_pan_and_zoom', $(this).prop('checked')); }); $('#stop_updates_when_focus_is_lost').change(function () { urlOptions.update_always = !$(this).prop('checked'); urlOptions.hashUpdate(); NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always); }); $('#smooth_plot').change(function () { NETDATA.setOption('smooth_plot', $(this).prop('checked')); }); $('#pan_and_zoom_data_padding').change(function () { NETDATA.setOption('pan_and_zoom_data_padding', $(this).prop('checked')); }); $('#seconds_as_time').change(function () { NETDATA.setOption('seconds_as_time', $(this).prop('checked')); }); $('#local_timezone').change(function () { if ($(this).prop('checked')) { selected_server_timezone('default', true); } else { selected_server_timezone('default', false); } }); $('#units_conversion').change(function () { NETDATA.setOption('units', $(this).prop('checked') ? 'auto' : 'original'); }); $('#units_temp').change(function () { NETDATA.setOption('temperature', $(this).prop('checked') ? 'celsius' : 'fahrenheit'); }); $('#show_help').change(function () { urlOptions.help = $(this).prop('checked'); urlOptions.hashUpdate(); NETDATA.setOption('show_help', urlOptions.help); netdataReload(); }); // this has to be the last // it reloads the page $('#netdata_theme_control').change(function () { urlOptions.theme = $(this).prop('checked') ? 'slate' : 'white'; urlOptions.hashUpdate(); if (setTheme(urlOptions.theme)) { netdataReload(); } }); } function scrollDashboardTo() { if (netdataSnapshotData !== null && typeof netdataSnapshotData.hash !== 'undefined') { //console.log(netdataSnapshotData.hash); scrollToId(netdataSnapshotData.hash.replace('#', '')); } else { // check if we have to jump to a specific section scrollToId(urlOptions.hash.replace('#', '')); if (urlOptions.chart !== null) { NETDATA.alarms.scrollToChart(urlOptions.chart); //urlOptions.hash = '#' + NETDATA.name2id('menu_' + charts[c].menu + '_submenu_' + charts[c].submenu); //urlOptions.hash = '#chart_' + NETDATA.name2id(urlOptions.chart); //console.log('hash = ' + urlOptions.hash); } } } var modalHiddenCallback = null; function scrollToChartAfterHidingModal(chart, alarmDate, alarmStatus) { modalHiddenCallback = function () { NETDATA.alarms.scrollToChart(chart, alarmDate); if (['WARNING', 'CRITICAL'].includes(alarmStatus)) { const currentChartState = NETDATA.options.targets.find( (chartState) => chartState.id === chart, ) const twoMinutes = 2 * 60 * 1000 NETDATA.globalPanAndZoom.setMaster( currentChartState, alarmDate - twoMinutes, alarmDate + twoMinutes, ) } }; } // ---------------------------------------------------------------------------- function enableTooltipsAndPopovers() { $('[data-toggle="tooltip"]').tooltip({ animated: 'fade', trigger: 'hover', html: true, delay: { show: 500, hide: 0 }, container: 'body' }); $('[data-toggle="popover"]').popover(); } // ---------------------------------------------------------------------------- var runOnceOnDashboardLastRun = 0; function runOnceOnDashboardWithjQuery() { if (runOnceOnDashboardLastRun !== 0) { scrollDashboardTo(); // restore the scrollspy at the proper position $(document.body).scrollspy('refresh'); $(document.body).scrollspy('process'); return; } runOnceOnDashboardLastRun = Date.now(); // ------------------------------------------------------------------------ // bootstrap modals // prevent bootstrap modals from scrolling the page // maintains the current scroll position // https://stackoverflow.com/a/34754029/4525767 var scrollPos = 0; var modal_depth = 0; // how many modals are currently open var modal_shown = false; // set to true, if a modal is shown var netdata_paused_on_modal = false; // set to true, if the modal paused netdata var scrollspyOffset = $(window).height() / 3; // will be updated below - the offset of scrollspy to select an item $('.modal') .on('show.bs.modal', function () { if (modal_depth === 0) { scrollPos = window.scrollY; $('body').css({ overflow: 'hidden', position: 'fixed', top: -scrollPos }); modal_shown = true; if (NETDATA.options.pauseCallback === null) { NETDATA.pause(function () { }); netdata_paused_on_modal = true; } else { netdata_paused_on_modal = false; } } modal_depth++; //console.log(urlOptions.after); }) .on('hide.bs.modal', function () { modal_depth--; if (modal_depth <= 0) { modal_depth = 0; $('body') .css({ overflow: '', position: '', top: '' }); // scroll to the position we had open before the modal $('html, body') .animate({ scrollTop: scrollPos }, 0); // unpause netdata, if we paused it if (netdata_paused_on_modal === true) { NETDATA.unpause(); netdata_paused_on_modal = false; } // restore the scrollspy at the proper position $(document.body).scrollspy('process'); } //console.log(urlOptions.after); }) .on('hidden.bs.modal', function () { if (modal_depth === 0) { modal_shown = false; } if (typeof modalHiddenCallback === 'function') { modalHiddenCallback(); } modalHiddenCallback = null; //console.log(urlOptions.after); }); // ------------------------------------------------------------------------ // sidebar / affix if (shouldShowSignInBanner()) { const el = document.getElementById("sign-in-banner"); if (el) { el.style.display = "initial"; el.classList.add(`theme-${netdataTheme}`); } } $('#sidebar') .affix({ offset: { top: (isdemo()) ? 150 : 0, bottom: 0 } }) .on('affixed.bs.affix', function () { // fix scrolling of very long affix lists // http://stackoverflow.com/questions/21691585/bootstrap-3-1-0-affix-too-long $(this).removeAttr('style'); }) .on('affix-top.bs.affix', function () { // fix bootstrap affix click bug // https://stackoverflow.com/a/37847981/4525767 if (modal_shown) { return false; } }) .on('activate.bs.scrollspy', function (e) { // change the URL based on the current position of the screen if (modal_shown === false) { var el = $(e.target); var hash = el.find('a').attr('href'); if (typeof hash === 'string' && hash.substring(0, 1) === '#' && urlOptions.hash.startsWith(hash + '_submenu_') === false) { urlOptions.hash = hash; urlOptions.hashUpdate(); } } }); Ps.initialize(document.getElementById('sidebar'), { wheelSpeed: 0.5, wheelPropagation: true, swipePropagation: true, minScrollbarLength: null, maxScrollbarLength: null, useBothWheelAxes: false, suppressScrollX: true, suppressScrollY: false, scrollXMarginOffset: 0, scrollYMarginOffset: 0, theme: 'default' }); // ------------------------------------------------------------------------ // scrollspy if (scrollspyOffset > 250) { scrollspyOffset = 250; } if (scrollspyOffset < 75) { scrollspyOffset = 75; } document.body.setAttribute('data-offset', scrollspyOffset); // scroll the dashboard, before activating the scrollspy, so that our // hash will not be updated before we got the chance to scroll to it scrollDashboardTo(); $(document.body).scrollspy({ target: '#sidebar', offset: scrollspyOffset // controls the diff of the element to the top, to select it }); // ------------------------------------------------------------------------ // my-netdata menu Ps.initialize(document.getElementById('my-netdata-dropdown-content'), { wheelSpeed: 1, wheelPropagation: false, swipePropagation: false, minScrollbarLength: null, maxScrollbarLength: null, useBothWheelAxes: false, suppressScrollX: true, suppressScrollY: false, scrollXMarginOffset: 0, scrollYMarginOffset: 0, theme: 'default' }); $('#myNetdataDropdownParent') .on('show.bs.dropdown', function () { var hash = urlOptions.genHash(); $('.registry_link').each(function (idx) { this.setAttribute('href', this.getAttribute("href").replace(/#.*$/, hash)); }); NETDATA.pause(function () { }); }) .on('shown.bs.dropdown', function () { Ps.update(document.getElementById('my-netdata-dropdown-content')); myNetdataMenuDidShow(); }) .on('hidden.bs.dropdown', function () { NETDATA.unpause(); }); $('#deleteRegistryModal') .on('hidden.bs.modal', function () { deleteRegistryGuid = null; }); // ------------------------------------------------------------------------ // update modal $('#updateModal') .on('show.bs.modal', function () { versionLog('checking, please wait...'); }) .on('shown.bs.modal', function () { notifyForUpdate(true); }); // ------------------------------------------------------------------------ // alarms modal $('#alarmsModal') .on('shown.bs.modal', function () { alarmsUpdateModal(); }) .on('hidden.bs.modal', function () { document.getElementById('alarms_active').innerHTML = document.getElementById('alarms_all').innerHTML = document.getElementById('alarms_log').innerHTML = 'loading...'; }); // ------------------------------------------------------------------------ dashboardSettingsSetup(); loadSnapshotDragAndDropSetup(); saveSnapshotModalSetup(); showPageFooter(); // ------------------------------------------------------------------------ // https://github.com/viralpatel/jquery.shorten/blob/master/src/jquery.shorten.js $.fn.shorten = function (settings) { "use strict"; var config = { showChars: 750, minHideChars: 10, ellipsesText: "...", moreText: ' show more information', lessText: ' show less information', onLess: function () { NETDATA.onscroll(); }, onMore: function () { NETDATA.onscroll(); }, errMsg: null, force: false }; if (settings) { $.extend(config, settings); } if ($(this).data('jquery.shorten') && !config.force) { return false; } $(this).data('jquery.shorten', true); $(document).off("click", '.morelink'); $(document).on({ click: function () { var $this = $(this); if ($this.hasClass('less')) { $this.removeClass('less'); $this.html(config.moreText); $this.parent().prev().animate({ 'height': '0' + '%' }, 0, function () { $this.parent().prev().prev().show(); }).hide(0, function () { config.onLess(); }); } else { $this.addClass('less'); $this.html(config.lessText); $this.parent().prev().animate({ 'height': '100' + '%' }, 0, function () { $this.parent().prev().prev().hide(); }).show(0, function () { config.onMore(); }); } return false; } }, '.morelink'); return this.each(function () { var $this = $(this); var content = $this.html(); var contentlen = $this.text().length; if (contentlen > config.showChars + config.minHideChars) { var c = content.substr(0, config.showChars); if (c.indexOf('<') >= 0) // If there's HTML don't want to cut it { var inTag = false; // I'm in a tag? var bag = ''; // Put the characters to be shown here var countChars = 0; // Current bag size var openTags = []; // Stack for opened tags, so I can close them later var tagName = null; for (var i = 0, r = 0; r <= config.showChars; i++) { if (content[i] === '<' && !inTag) { inTag = true; // This could be "tag" or "/tag" tagName = content.substring(i + 1, content.indexOf('>', i)); // If its a closing tag if (tagName[0] === '/') { if (tagName !== ('/' + openTags[0])) { config.errMsg = 'ERROR en HTML: the top of the stack should be the tag that closes'; } else { openTags.shift(); // Pops the last tag from the open tag stack (the tag is closed in the retult HTML!) } } else { // There are some nasty tags that don't have a close tag like
if (tagName.toLowerCase() !== 'br') { openTags.unshift(tagName); // Add to start the name of the tag that opens } } } if (inTag && content[i] === '>') { inTag = false; } if (inTag) { bag += content.charAt(i); } else { // Add tag name chars to the result r++; if (countChars <= config.showChars) { bag += content.charAt(i); // Fix to ie 7 not allowing you to reference string characters using the [] countChars++; } else { // Now I have the characters needed if (openTags.length > 0) { // I have unclosed tags //console.log('They were open tags'); //console.log(openTags); for (var j = 0; j < openTags.length; j++) { //console.log('Cierro tag ' + openTags[j]); bag += ''; // Close all tags that were opened // You could shift the tag from the stack to check if you end with an empty stack, that means you have closed all open tags } break; } } } } c = $('
').html(bag + '' + config.ellipsesText + '').html(); } else { c += config.ellipsesText; } var html = '
' + c + '
' + content + '
' + config.moreText + ''; $this.html(html); $this.find(".allcontent").hide(); // Hide all text $('.shortcontent p:last', $this).css('margin-bottom', 0); //Remove bottom margin on last paragraph as it's likely shortened } }); }; } function finalizePage() { // resize all charts - without starting the background thread // this has to be done while NETDATA is paused // if we omit this, the affix menu will be wrong, since all // the Dom elements are initially zero-sized NETDATA.parseDom(); // ------------------------------------------------------------------------ NETDATA.globalPanAndZoom.callback = null; NETDATA.globalChartUnderlay.callback = null; if (urlOptions.pan_and_zoom === true && NETDATA.options.targets.length > 0) { NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], urlOptions.after, urlOptions.before); } // callback for us to track PanAndZoom operations NETDATA.globalPanAndZoom.callback = urlOptions.netdataPanAndZoomCallback; NETDATA.globalChartUnderlay.callback = urlOptions.netdataHighlightCallback; // ------------------------------------------------------------------------ // let it run (update the charts) NETDATA.unpause(); runOnceOnDashboardWithjQuery(); $(".shorten").shorten(); enableTooltipsAndPopovers(); if (isdemo()) { // do not to give errors on netdata demo servers for 60 seconds NETDATA.options.current.retries_on_data_failures = 60; // google analytics when this is used for the home page of the demo sites // this does not run on user's installations setTimeout(function () { (function (i, s, o, g, r, a, m) { i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () { (i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o), m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m) })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); ga('create', 'UA-64295674-3', 'auto'); ga('send', 'pageview', '/demosite/' + window.location.host); }, 2000); } else { notifyForUpdate(); } if (urlOptions.show_alarms === true) { setTimeout(function () { $('#alarmsModal').modal('show'); }, 1000); } NETDATA.onresizeCallback = function () { Ps.update(document.getElementById('sidebar')); Ps.update(document.getElementById('my-netdata-dropdown-content')); }; NETDATA.onresizeCallback(); if (netdataSnapshotData !== null) { NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], netdataSnapshotData.after_ms, netdataSnapshotData.before_ms); } //if (urlOptions.nowelcome !== true) { // setTimeout(function () { // $('#welcomeModal').modal(); // }, 2000); //} // var netdataEnded = performance.now(); // console.log('start up time: ' + (netdataEnded - netdataStarted).toString() + ' ms'); } function resetDashboardOptions() { var help = NETDATA.options.current.show_help; NETDATA.resetOptions(); if (setTheme('slate')) { netdataReload(); } if (help !== NETDATA.options.current.show_help) { netdataReload(); } } // callback to add the dashboard info to the // parallel javascript downloader in netdata var netdataPrepCallback = function () { NETDATA.requiredCSS.push({ url: NETDATA.serverStatic + 'css/bootstrap-toggle-2.2.2.min.css', isAlreadyLoaded: function () { return false; } }); NETDATA.requiredJs.push({ url: NETDATA.serverStatic + 'lib/bootstrap-toggle-2.2.2.min.js', isAlreadyLoaded: function () { return false; } }); NETDATA.requiredJs.push({ url: NETDATA.serverStatic + 'dashboard_info.js?v20181019-1', async: false, isAlreadyLoaded: function () { return false; } }); if (isdemo()) { document.getElementById('masthead').style.display = 'block'; } else { if (urlOptions.update_always === true) { NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always); } } }; var selected_server_timezone = function (timezone, status) { //console.log('called with timezone: ' + timezone + ", status: " + ((typeof status === 'undefined')?'undefined':status).toString()); // clear the error document.getElementById('timezone_error_message').innerHTML = ''; if (typeof status === 'undefined') { // the user selected a timezone from the menu NETDATA.setOption('user_set_server_timezone', timezone); if (NETDATA.dateTime.init(timezone) === false) { NETDATA.dateTime.init(); if (!$('#local_timezone').prop('checked')) { $('#local_timezone').bootstrapToggle('on'); } document.getElementById('timezone_error_message').innerHTML = 'Ooops! That timezone was not accepted by your browser. Please open a github issue to help us fix it.'; NETDATA.setOption('user_set_server_timezone', NETDATA.options.server_timezone); } else { if ($('#local_timezone').prop('checked')) { $('#local_timezone').bootstrapToggle('off'); } } } else if (status === true) { // the user wants the browser default timezone to be activated NETDATA.dateTime.init(); } else { // the user wants the server default timezone to be activated //console.log('found ' + NETDATA.options.current.user_set_server_timezone); if (NETDATA.options.current.user_set_server_timezone === 'default') { NETDATA.options.current.user_set_server_timezone = NETDATA.options.server_timezone; } timezone = NETDATA.options.current.user_set_server_timezone; if (NETDATA.dateTime.init(timezone) === false) { NETDATA.dateTime.init(); if (!$('#local_timezone').prop('checked')) { $('#local_timezone').bootstrapToggle('on'); } document.getElementById('timezone_error_message').innerHTML = 'Sorry. The timezone "' + timezone.toString() + '" is not accepted by your browser. Please select one from the list.'; NETDATA.setOption('user_set_server_timezone', NETDATA.options.server_timezone); } } document.getElementById('current_timezone').innerText = (NETDATA.options.current.timezone === 'default') ? 'unset, using browser default' : NETDATA.options.current.timezone; return false; }; // our entry point // var netdataStarted = performance.now(); var netdataCallback = initializeDynamicDashboard; // ================================================================================================= // netdata.cloud let registryAgents = []; let cloudAgents = []; let myNetdataMenuFilterValue = ""; let cloudAccountID = null; let cloudAccountName = null; let cloudToken = null; /// Enforces a maximum string length while retaining the prefix and the postfix of /// the string. function truncateString(str, maxLength) { if (str.length <= maxLength) { return str; } const spanLength = Math.floor((maxLength - 3) / 2); return `${str.substring(0, spanLength)}...${str.substring(str.length - spanLength)}`; } // ------------------------------------------------------------------------------------------------- // netdata.cloud API Client // ------------------------------------------------------------------------------------------------- function isValidAgent(a) { return a.urls != null && a.urls.length > 0; } // https://github.com/netdata/hub/issues/146 function getCloudAccountAgents() { if (!isSignedIn()) { return []; } return fetch( `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`, { method: "GET", mode: "cors", headers: { "Authorization": `Bearer ${cloudToken}` } } ).then((response) => { if (!response.ok) { throw Error("Cannot fetch known accounts"); } return response.json(); }).then((payload) => { const agents = payload.result ? payload.result.agents : null; if (!agents) { return []; } return agents.filter((a) => isValidAgent(a)).map((a) => { return { "guid": a.id, "name": a.name, "url": a.urls[0], "alternate_urls": a.urls } }) }).catch(function (error) { console.log(error); return null; }); } /** Updates the lastAccessTime and accessCount properties of the agent for the account. */ function touchAgent() { if (!isSignedIn()) { return []; } const touchUrl = `${NETDATA.registry.cloudBaseURL}/api/v1/agents/${NETDATA.registry.machine_guid}/touch?account_id=${cloudAccountID}`; return fetch( touchUrl, { method: "post", body: "", mode: "cors", headers: { "Authorization": `Bearer ${cloudToken}` } } ).then((response) => { if (!response.ok) { throw Error("Cannot touch agent" + JSON.stringify(response)); } return response.json(); }).then((payload) => { }).catch(function (error) { console.log(error); return null; }); } // https://github.com/netdata/hub/issues/128 function postCloudAccountAgents(agentsToSync) { if (!isSignedIn()) { return []; } const maskedURL = NETDATA.registry.MASKED_DATA; const agents = agentsToSync.map((a) => { const urls = a.alternate_urls.filter((url) => url != maskedURL); return { "id": a.guid, "name": a.name, "urls": urls } }).filter((a) => isValidAgent(a)) const payload = { "accountID": cloudAccountID, "agents": agents, "merge": false, }; return fetch( `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`, { method: "POST", mode: "cors", headers: { "Content-Type": "application/json; charset=utf-8", "Authorization": `Bearer ${cloudToken}` }, body: JSON.stringify(payload) } ).then((response) => { return response.json(); }).then((payload) => { const agents = payload.result ? payload.result.agents : null; if (!agents) { return []; } return agents.filter((a) => isValidAgent(a)).map((a) => { return { "guid": a.id, "name": a.name, "url": a.urls[0], "alternate_urls": a.urls } }) }); } function deleteCloudAgentURL(agentID, url) { if (!isSignedIn()) { return []; } return fetch( `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents/${agentID}/url?value=${encodeURIComponent(url)}`, { method: "DELETE", mode: "cors", headers: { "Content-Type": "application/json; charset=utf-8", "Authorization": `Bearer ${cloudToken}` }, } ).then((response) => { return response.json(); }).then((payload) => { const count = payload.result ? payload.result.count : 0; return count; }); } // ------------------------------------------------------------------------------------------------- function signInDidClick(e) { e.preventDefault(); e.stopPropagation(); if (!NETDATA.registry.isUsingGlobalRegistry()) { // If user is using a private registry, request his consent for // synchronizing with cloud. showSignInModal(); return; } signIn(); } function shouldShowSignInBanner() { return false; } function closeSignInBanner() { localStorage.setItem("signInBannerClosed", "true"); const el = document.getElementById("sign-in-banner"); if (el) { el.style.display = "none"; } } function closeSignInBannerDidClick(e) { closeSignInBanner(); } function signOutDidClick(e) { e.preventDefault(); e.stopPropagation(); signOut(); } // ------------------------------------------------------------------------------------------------- function updateMyNetdataAfterFilterChange() { const machinesEl = document.getElementById("my-netdata-menu-machines") machinesEl.innerHTML = renderMachines(cloudAgents); if (options.hosts.length > 1) { const streamedEl = document.getElementById("my-netdata-menu-streamed") streamedEl.innerHTML = renderStreamedHosts(options); } } function myNetdataMenuDidShow() { const filterEl = document.getElementById("my-netdata-menu-filter-input"); if (filterEl) { filterEl.focus(); } } function myNetdataFilterDidChange(e) { const inputEl = e.target; setTimeout(() => { myNetdataMenuFilterValue = inputEl.value; updateMyNetdataAfterFilterChange(); }, 1); } function myNetdataFilterClearDidClick(e) { e.preventDefault(); e.stopPropagation(); const inputEl = document.getElementById("my-netdata-menu-filter-input"); inputEl.value = ""; myNetdataMenuFilterValue = ""; updateMyNetdataAfterFilterChange(); inputEl.focus(); } // ------------------------------------------------------------------------------------------------- function clearCloudVariables() { cloudAccountID = null; cloudAccountName = null; cloudToken = null; } function clearCloudLocalStorageItems() { localStorage.removeItem("cloud.baseURL"); localStorage.removeItem("cloud.agentID"); localStorage.removeItem("cloud.sync"); } function signIn() { const url = `${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}`; window.open(url); } function signOut() { cloudSSOSignOut(); } function handleMessage(e) { switch (e.data.type) { case "sign-in": handleSignInMessage(e); break; case "sign-out": handleSignOutMessage(e); break; default: return; } } function handleSignInMessage(e) { closeSignInBanner(); localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL); cloudAccountID = e.data.accountID; cloudAccountName = e.data.accountName; cloudToken = e.data.token; netdataRegistryCallback(registryAgents); if (e.data.redirectURI && !window.location.href.includes(e.data.redirectURI)) { // lgtm false-positive - redirectURI does not come from user input, but from iframe callback window.location.replace(e.data.redirectURI); // lgtm[js/client-side-unvalidated-url-redirection] } } function handleSignOutMessage(e) { clearCloudVariables(); renderMyNetdataMenu(registryAgents); } function isSignedIn() { return cloudToken != null && cloudAccountID != null; } function sortedArraysEqual(a, b) { if (a.length != b.length) return false; for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) return false; } return true; } // If merging is needed returns the merged agents set, otherwise returns null. function mergeAgents(cloud, local) { let dirty = false; const union = new Map(); for (const cagent of cloud) { union.set(cagent.guid, cagent); } for (const lagent of local) { const cagent = union.get(lagent.guid); if (cagent) { for (const u of lagent.alternate_urls) { if (u === NETDATA.registry.MASKED_DATA) { // TODO: temp until registry is updated. continue; } if (!cagent.alternate_urls.includes(u)) { dirty = true; cagent.alternate_urls.push(u); } } } else { dirty = true; union.set(lagent.guid, lagent); } } if (dirty) { return Array.from(union.values()); } return null; } function showSignInModal() { document.getElementById("sim-registry").innerHTML = NETDATA.registry.server; $("#signInModal").modal("show"); } function explicitlySignIn() { $("#signInModal").modal("hide"); signIn(); } function showSyncModal() { document.getElementById("sync-registry-modal-registry").innerHTML = NETDATA.registry.server; $("#syncRegistryModal").modal("show"); } function explicitlySyncAgents() { $("#syncRegistryModal").modal("hide"); const json = localStorage.getItem("cloud.sync"); const sync = json ? JSON.parse(json) : {}; delete sync[cloudAccountID]; localStorage.setItem("cloud.sync", JSON.stringify(sync)); NETDATA.registry.init(); } function syncAgents(callback) { const json = localStorage.getItem("cloud.sync"); const sync = json ? JSON.parse(json) : {}; const currentAgent = { guid: NETDATA.registry.machine_guid, name: NETDATA.registry.hostname, url: NETDATA.serverDefault, alternate_urls: [NETDATA.serverDefault], } const localAgents = sync[cloudAccountID] ? [currentAgent] : registryAgents.concat([currentAgent]); console.log("Checking if sync is needed.", localAgents); const agentsToSync = mergeAgents(cloudAgents, localAgents); if ((!sync[cloudAccountID]) || agentsToSync) { sync[cloudAccountID] = new Date().getTime(); localStorage.setItem("cloud.sync", JSON.stringify(sync)); } if (agentsToSync) { console.log("Synchronizing with netdata.cloud."); postCloudAccountAgents(agentsToSync).then((agents) => { // TODO: clear syncTime on error! cloudAgents = agents; callback(cloudAgents); }); return } callback(cloudAgents); } let isCloudSSOInitialized = false; function cloudSSOInit() { const iframeEl = document.getElementById("ssoifrm"); const url = `${NETDATA.registry.cloudBaseURL}/account/sso-agent?id=${NETDATA.registry.machine_guid}`; iframeEl.src = url; isCloudSSOInitialized = true; } function cloudSSOSignOut() { const iframe = document.getElementById("ssoifrm"); const url = `${NETDATA.registry.cloudBaseURL}/account/sign-out-agent`; iframe.src = url; } function initCloud() { if (!NETDATA.registry.isCloudEnabled) { clearCloudVariables(); clearCloudLocalStorageItems(); return; } if (NETDATA.registry.cloudBaseURL != localStorage.getItem("cloud.baseURL")) { clearCloudVariables(); clearCloudLocalStorageItems(); if (NETDATA.registry.cloudBaseURL) { localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL); } } if (!isCloudSSOInitialized) { cloudSSOInit(); } touchAgent(); } // This callback is called after NETDATA.registry is initialized. function netdataRegistryCallback(machinesArray) { localStorage.setItem("cloud.agentID", NETDATA.registry.machine_guid); initCloud(); registryAgents = machinesArray; if (isSignedIn()) { // We call getCloudAccountAgents() here because it requires that // NETDATA.registry is initialized. clearMyNetdataMenu(); getCloudAccountAgents().then((agents) => { if (!agents) { errorMyNetdataMenu(); return; } cloudAgents = agents; syncAgents((agents) => { const agentsMap = {} for (const agent of agents) { agentsMap[agent.guid] = agent; } NETDATA.registry.machines = agentsMap; NETDATA.registry.machines_array = agents; renderMyNetdataMenu(agents); }); }); } else { renderMyNetdataMenu(machinesArray) } }; // If we know the cloudBaseURL and agentID from local storage render (eagerly) // the account ui before receiving the definitive response from the web server. // This improves the perceived performance. function tryFastInitCloud() { const baseURL = localStorage.getItem("cloud.baseURL"); const agentID = localStorage.getItem("cloud.agentID"); if (baseURL && agentID) { NETDATA.registry.cloudBaseURL = baseURL; NETDATA.registry.machine_guid = agentID; NETDATA.registry.isCloudEnabled = true; initCloud(); } } function initializeApp() { window.addEventListener("message", handleMessage, false); // tryFastInitCloud(); } if (document.readyState === "complete") { initializeApp(); } else { document.addEventListener("readystatechange", () => { if (document.readyState === "complete") { initializeApp(); } }); }