%PDF-1.5 %���� ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY
Server IP : 49.231.201.246 / Your IP : 216.73.216.149 Web Server : Apache/2.4.18 (Ubuntu) System : Linux 246 4.4.0-210-generic #242-Ubuntu SMP Fri Apr 16 09:57:56 UTC 2021 x86_64 User : root ( 0) PHP Version : 7.0.33-0ubuntu0.16.04.16 Disable Function : exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/share/webmin/authentic-theme/extensions/stats/ |
Upload File : |
/*! * Authentic Theme (https://github.com/authentic-theme/authentic-theme) * Copyright Ilia Rostovtsev <ilia@virtualmin.com> * Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE) */ /* jshint strict: true */ /* jshint esversion: 6 */ /* jshint jquery: true */ "use strict"; // Stats module const stats = { sys: { // Define variables error: 0, tried: 0, activating: 0, requery: null, socket: null, // Import globals /* jshint -W117 */ _: { prefix: v___location_prefix, error: connection_error, language: theme_language, convert: { size: Convert.nice_size, }, chart: Chartist, dayjs: dayjs, locale: { time: config_portable_theme_locale_format_time, offset: () => { return get_utc_offset(); }, }, can_conn_ws: can_conn_ws, blocked: theme_updating, getHistoryData: function () { return vars.stats.history; }, }, // Define reusable selectors selector: { chart: { container: { parent: "live_stats", data: "data-chart", }, loader: "data-charts-loader", }, collapse: "collapse", dashboard: "system-status", slider: "info-container", piechart: "piechart", defaultClassLabel: "bg-semi-transparent", defaultSliderClassLabel: "bg-semi-transparent-dark", }, // Get current data to submit to the socket getSocketDefs: function () { return { session: session.server.data("session-hash"), paused: !this.canRender() ? 1 : 0, interval: this.getInterval(), disable: !this.isEnabled() ? 1 : 0, shutdown: settings_sysinfo_real_time_shutdown_on_last ? 1 : 0, }; }, // Update settings for the current client on the socket server // updateSocket: function () { // if (this.isEnabled() && this.socket && // this.socket.readyState === 1) { // const socketData = this.getSocketDefs(); // // Update socket settings // this.socket.send(JSON.stringify(socketData)); // } // }, // Check if graphs can be rendered graphsCanPreRender: function () { return document.querySelector(`[${this.selector.chart.loader}]`) ? 1 : 0; }, // Get interval call for the stats update getInterval: function () { return settings_sysinfo_real_time_run_rate / 1000; }, // Get the stored data duration period (e.g., from 300 to 86400 seconds) getStoredDuration: function () { return settings_sysinfo_real_time_stored_duration; }, // Check if the received data has multiple datasets getRenderType: function(graphs) { graphs = graphs.graphs; let hasMultipleDatasets = false; for (const key in graphs) { if (graphs.hasOwnProperty(key) && Array.isArray(graphs[key])) { if (graphs[key].length > 1) { hasMultipleDatasets = true; break; } } } // Received graphs stats have history // data (3) or a single slice (null) return hasMultipleDatasets ? 3 : null; }, // Can we update the stats in the UI? canRender: function() { return theme.visibility.get(); }, // Check if the stats are enabled isEnabled: function () { const stats_enabled = settings_sysinfo_real_time_status ? 1 : 0, stats_can = this._.can_conn_ws(); return stats_enabled && stats_can; }, // Restart the stats by shutting down and enabling them restart: function () { this.shutdown(); setTimeout(() => { this.enable(); }, this.getInterval() * 1000 * 4); }, // Disable the stats broadcast for the client disable: function () { if (this.socket && this.socket.readyState === 1) { // console.warn("Disabling stats broadcast"); const socketData = this.getSocketDefs(); socketData.paused = 1; this.socket.send(JSON.stringify(socketData)); } }, // Enable the stats broadcast for the client enable: function () { if (this.isEnabled()) { // console.warn("Enabling stats broadcast"); if (this.graphsCanPreRender()) { this.preRender(); } if (this.socket) { // console.warn("Sending ..", this.socket.readyState === 1), this.socket.readyState === 1 && this.socket.send(JSON.stringify(this.getSocketDefs())); } else { // console.warn("Activating .."), this.activate(); } } }, // Shutdown the stats broadcast for all // clients by shutting down the socket shutdown: function () { if (this.socket && this.socket.readyState === 1) { const socketData = this.getSocketDefs(); socketData.disable = 1; this.socket.send(JSON.stringify(socketData)); } }, // Activate the stats server unless already up and open // the socket to receive the data for the current tab activate: function () { // Already called for this tab? if (this.activating++ || this._.blocked() || this.socket) { return; } // Tried too many times? if (this.tried++ > 4) { return; } $.ajax({ context: this, url: `${this._.prefix}/stats.cgi`, error: function () { // Reset activating flag this.activating = 0; // Show error if (this.error++ > 3) { return; } // Retry again !this.requery && (this.requery = setTimeout(() => { this.requery = null; this.activate(); }, this.getInterval() * 1000 * 4)); }, success: function (data) { // Do we have socket opened? if (data.success) { // Open socket // console.warn("WebSocket connection opened", data); this.socket = new WebSocket(data.socket); // On socket open this.socket.onopen = () => { this.tried = 0; this.activating = 0; // console.log("WebSocket connection established", // this.getSocketDefs()); this.socket.send( JSON.stringify(this.getSocketDefs())); }; // On socket message this.socket.onmessage = (event) => { const message = JSON.parse(event.data), renderType = this.getRenderType(message); // Pause stats broadcast for this client // if the tab is not visible // err: no need, as redundant with enable/disable? // if (this.canRender.last != this.canRender()) { // console.log("Visibility changed", this.canRender()); // this.canRender.last = this.canRender(); // this.updateSocket(); // } // console.log("Received stats", renderType, message); this.render(message, renderType); }; // On socket close this.socket.onclose = () => { // console.warn("WebSocket connection closed"); setTimeout(() => { this.socket = null; this.activating = 0; this.enable(); }, this.getInterval() * 1000 * 4); }; } else { // Reset activating flag this.activating = 0; } // Reset error counter this.error = 0; }, dataType: "json", }); }, // Draw initial graphs either using stored data or empty placeholders preRender: function () { this.render(this._.getHistoryData(), 2); }, // Display changes render: function (data, graphs) { // Iterate through response Object.entries(data).map(([target, data]) => { let v = parseInt(data), vo = typeof data === "object" ? data[data.length - 1] : false, vt = vo ? vo : v, $pc = $( `#${this.selector.dashboard} .${this.selector.piechart}[data-charts*="${target}"]` ), $lc = $(`.${this.selector.slider} .${target}_percent`), $od = $(`#${this.selector.dashboard} span[data-id="sysinfo_${target}"], .${this.selector.slider} span[data-data="${target}"]`), cached = target === "graphs" ? (graphs ? (graphs === 3 ? 3 : 2) : (this.graphsCanPreRender() ? 2 : 1)) : 0; if (Number.isInteger(v)) { // Update pie-charts if ($pc.length) { let piechart = $pc.data("easyPieChart"); piechart && piechart.update(v); } // Update line-charts if ($lc.length) { $lc.find(".bar").attr("style", "width:" + v + "%"); // Update line-charts' text let $dp = $lc.find(".description"), $lb = $dp.text().split(":")[0], uv = $lb + ": " + v + "% (" + vo + ")"; // Flatten and plunk the data for some graphs if (target !== "cpu") { uv = plugins.slider.update.stats.graphs.flatten(uv); if (target !== "virt") { uv = plugins.slider.update.stats.graphs.plunk(uv); } } $dp.attr("title", vo).text(uv); } // Update other data if ($od.length) { if ($od.find("a").length) { $od.find("a").text(vt); } else { $od.text(vt); } } } // Update sensors data if (target === "sensors" && vo) { // Iterate through sensors Object.entries(vo).forEach(([sensor, value]) => { let this_ = this, $lb1 = $(`#${this.selector.dashboard} span[data-stats="${sensor}"]`), $lb2 = $(`.${this.selector.slider} span[data-stats="${sensor}"]`); if ($lb1 && $lb1.length) { let lb_count1 = $lb1.length, lb_count2 = $lb2.length; // Sort the values based on 'fan' or 'core' field value.sort((a, b) => sensor === "fans" ? a.fan - b.fan : a.core - b.core ); // Function to update individual label const updateLabel = function ($label, data, isSingleLabel, sideSlider) { if (!data) { return; } // Update the label text based on count condition if (isSingleLabel) { // Replace only the numeric part, preserving unit (°C or RPM) $label.html(function (_, html) { const iSFahrenheit = html.includes("°F"); return html.replace( /\d+/, sensor === "fans" ? data.rpm : (iSFahrenheit ? Math.round((data.temp * 9) / 5 + 32) : data.temp)); }); } else { // Replace the numeric part after the colon, preserving prefix and unit $label.html(function (_, html) { const iSFahrenheit = html.includes("°F"); return html.replace( /: \d+/, `: ${sensor === "fans" ? data.rpm : (iSFahrenheit ? Math.round((data.temp * 9) / 5 + 32) : data.temp)}` ); }); } const label_text = $label.text().replace(/.*?\d+:\s*/, ""); let className = HTML.label.textMaxLevels(sensor, label_text) || (sideSlider ? this_.selector.defaultSliderClassLabel : this_.selector.defaultClassLabel); if (sideSlider && className === this_.selector.defaultClassLabel) { className = this_.selector.defaultSliderClassLabel; } // Update class based on current label text $label .removeClass((i, c) => (c.match(/\bbg-\S+/g) || []).join(" ")) .addClass(className); }; // Handle $lb1 labels if (lb_count1 === 1) { updateLabel($lb1, value[0], true); } else { // Handle multiple labels for $lb1 $lb1.each((index, el) => { if (value[index]) { updateLabel($(el), value[index], false); } }); } // Handle $lb2 labels similarly if (lb_count2 === 1) { updateLabel($lb2, value[0], true, true); } else { $lb2.each((index, el) => { if (value[index]) { updateLabel($(el), value[index], false, true); } }); } } }); } // Draw history graphs if (cached) { let lds = `${this.selector.chart.container.parent}-${this.selector.collapse}`, ld = $(`#${lds}`).find(`[${this.selector.chart.loader}]`); // Process each supplied graph Object.entries(data).map(([type, array]) => { let options = { chart: { type: () => { return type === "proc" || type === "disk" || type === "net"; }, bandwidth: () => { return type === "disk" || type === "net"; }, fill: function () { return this.type() ? false : true; }, high: function () { return this.type() ? undefined : 100; }, threshold: function () { return this.type() ? -1 : 80; }, height: "100px", }, }, lg = this._.language(`${this.selector.chart.container.parent}_${type}`), tg = $(`#${lds}`).find( `[${this.selector.chart.container.data}=${type}]` ), sr = [ { name: `series-${type}`, data: array, }, ]; // Don't run further in case there is no container if (!tg.length) { return; } // Extend data object expected way to draw multiple series in single graph if (array[0] && typeof array[0].y === "object") { sr = []; array[0].y.forEach(function (x, i) { let data = []; array.forEach(function (n) { data.push({ data: { x: n.x, y: n.y[i] }, }); }); sr.push({ name: `series-${type}-${i}`, data: data, }); }); } // Update series if chart already // exist unless it's a re-draw if (tg[0] && tg[0].textContent && cached !== 3) { if (cached === 1) { let lf = parseInt(this.getStoredDuration()); if (lf < 300 || lf > 86400) { lf = 1200; } let tdata = sr, cdata = this[`chart_${type}`].data.series, cdata_start, cdata_end, cdata_ready = new Promise((resolve) => { tdata.forEach(function (d, i, a) { cdata_start = cdata[i].data[0].x || cdata[i].data[0].data.x; cdata_end = cdata[i].data[cdata[i].data.length - 1].x || cdata[i].data[cdata[i].data.length - 1].data.x; cdata[i].data.push(d.data[0]); if (cdata_end - cdata_start > lf) { cdata[i].data.shift(); } if (i === a.length - 1) { resolve(); } }); }); cdata_ready.then(() => { this[`chart_${type}`].update({ series: cdata, }); }); } } // Initialize chart the first time (2) or fully // update (3) the chart if it's already drawn else if (cached === 2 || cached === 3) { this[`chart_${type}`] = new this._.chart.Line( tg[0], { series: sr, }, { axisX: { type: this._.chart.FixedScaleAxis, divisor: 12, labelInterpolationFnc: (value) => { return this._.dayjs(value * 1000) .utcOffset(this._.locale.offset()) .format(this._.locale.time); }, }, height: options.chart.height, showArea: options.chart.fill(), showPoint: !options.chart.fill(), high: options.chart.high(), low: 0, fullWidth: true, chartPadding: { left: 25, }, axisY: { onlyInteger: true, labelInterpolationFnc: (value) => { if (options.chart.fill()) { return value ? value + "%" : value; } else if (options.chart.bandwidth(value)) { if (type === "net") { return value ? this._.convert.size(value, { fixed: 0, bits: 1, round: 1, }) : value; } return value ? this._.convert.size(value * 1024, { fixed: 0, round: 1, }) : value; } else { return value; } }, }, plugins: [ this._.chart.plugins.ctAxisTitle({ axisY: { axisTitle: lg, axisClass: "ct-axis-title", offset: { x: 0, y: 9, }, flipTitle: true, }, }), this._.chart.plugins.ctThreshold({ threshold: options.chart.threshold(), }), ], } ); // Remove loader this[`chart_${type}`].on("created", (data) => { // Add labels to the first foreign object const ffObj = data.svg.getNode().querySelector('foreignObject'); if (ffObj) { const readLbl = this._.language(`dashboard_chart_${type}_read`), writeLbl = this._.language(`dashboard_chart_${type}_write`); if (readLbl && writeLbl) { ffObj.setAttribute('data-label-read', `▪ ${readLbl}`); ffObj.setAttribute('data-label-write', `▪ ${writeLbl}`); } } // Clean-up loader ld.remove(); }); } }); } }); }, }, };