🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 gSpotx2f
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
#
# Copyright (C) 2026 gSpot (https://github.com/gSpotx2f/luci-app-cpu-status)
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-cpu-status
PKG_VERSION:=0.6.2
PKG_RELEASE:=1
LUCI_TITLE:=CPU utilization info for the LuCI status page
LUCI_PKGARCH:=all
PKG_LICENSE:=MIT
#include $(TOPDIR)/feeds/luci/luci.mk
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+42
View File
@@ -0,0 +1,42 @@
# luci-app-cpu-status
CPU utilization info for the LuCI status page (OpenWrt webUI).
OpenWrt >= 21.02.
Lite version: [luci-app-cpu-status-mini](https://github.com/gSpotx2f/luci-app-cpu-status-mini).
## Installation notes
**OpenWrt >= 25.12:**
wget --no-check-certificate -O /tmp/luci-app-cpu-status-0.6.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-cpu-status-0.6.2-r1.apk
apk --allow-untrusted add /tmp/luci-app-cpu-status-0.6.2-r1.apk
rm /tmp/luci-app-cpu-status-0.6.2-r1.apk
service rpcd reload
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-cpu-status-ru-0.6.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-cpu-status-ru-0.6.2-r1.apk
apk --allow-untrusted add /tmp/luci-i18n-cpu-status-ru-0.6.2-r1.apk
rm /tmp/luci-i18n-cpu-status-ru-0.6.2-r1.apk
**OpenWrt <= 24.10:**
wget --no-check-certificate -O /tmp/luci-app-cpu-status_0.6.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-cpu-status_0.6.2-r1_all.ipk
opkg install /tmp/luci-app-cpu-status_0.6.2-r1_all.ipk
rm /tmp/luci-app-cpu-status_0.6.2-r1_all.ipk
service rpcd reload
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-cpu-status-ru_0.6.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-cpu-status-ru_0.6.2-r1_all.ipk
opkg install /tmp/luci-i18n-cpu-status-ru_0.6.2-r1_all.ipk
rm /tmp/luci-i18n-cpu-status-ru_0.6.2-r1_all.ipk
## Screenshots:
![](https://github.com/gSpotx2f/luci-app-cpu-status/blob/master/screenshots/01.jpg)
## Related LuCI applications:
CPU performance: [https://github.com/gSpotx2f/luci-app-cpu-perf](https://github.com/gSpotx2f/luci-app-cpu-perf).
@@ -0,0 +1,18 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<polyline id="total_line" class="total" points="" />
<polyline id="user_line" class="user" points="" />
<polyline id="sys_line" class="sys" points="" />
<polyline id="sirq_line" class="sirq" points="" />
<line class="grid" x1="0" y1="25%" x2="100%" y2="25%" />
<text id="label_75" x="20" y="24%"> </text>
<line class="grid" x1="0" y1="50%" x2="100%" y2="50%" />
<text id="label_50" x="20" y="49%"> </text>
<line class="grid" x1="0" y1="75%" x2="100%" y2="75%" />
<text id="label_25" x="20" y="74%"> </text>
</svg>

After

Width:  |  Height:  |  Size: 747 B

@@ -0,0 +1,509 @@
'use strict';
'require dom';
'require fs';
'require poll';
'require request';
'require view';
'require ui';
document.head.append(E('style', {'type': 'text/css'},
`
:root {
--app-cpu-status-total: gray;
--app-cpu-status-user: blue;
--app-cpu-status-sys: red;
--app-cpu-status-sirq: green;
}
.svg_background {
width: 100%;
height: 300px;
border: 1px solid #000;
background: #fff';
}
[data-darkmode="true"] .svg_background {
background-color: var(--background-color-high) !important;
}
.graph_legend {
border-bottom: 2px solid;
}
.total {
border-color: var(--app-cpu-status-total);
}
.user{
border-color: var(--app-cpu-status-user);
}
.sys {
border-color: var(--app-cpu-status-sys);
}
.sirq {
border-color: var(--app-cpu-status-sirq);
}
svg line.grid {
stroke: black;
stroke-width: 0.1;
}
[data-darkmode="true"] svg line.grid {
stroke: #fff !important;
}
svg text {
fill: #eee;
font-size: 9pt;
font-family: sans-serif;
text-shadow: 1px 1px 1px #000;
}
svg #total_line {
fill: var(--app-cpu-status-total);
fill-opacity: 0.4;
stroke: var(--app-cpu-status-total);
stroke-width: 1;
}
svg #user_line {
fill: var(--app-cpu-status-user);
fill-opacity: 0.5;
stroke: var(--app-cpu-status-user);
stroke-width: 1;
}
svg #sys_line {
fill: var(--app-cpu-status-sys);
fill-opacity: 0.5;
stroke: var(--app-cpu-status-sys);
stroke-width: 1;
}
svg #sirq_line {
fill: var(--app-cpu-status-sirq);
fill-opacity: 0.5;
stroke: var(--app-cpu-status-sirq);
stroke-width: 1;
}
`));
Math.log2 = Math.log2 || (x => Math.log(x) * Math.LOG2E);
return view.extend({
pollInterval : 3,
dataBufferSize: 4,
CPUDevices : {},
graphPolls : [],
lastStatArray : null,
parseProcData(data) {
let cpu_stat_array = [];
let stat_items_array = data.trim().split('\n').filter(s => s.startsWith('cpu'));
for(let str of stat_items_array) {
let arr = str.split(/\s+/).slice(0, 8);
arr[0] = (arr[0] === 'cpu') ? -1 : arr[0].replace('cpu', '');
cpu_stat_array.push(arr.map(e => Number(e)));
};
cpu_stat_array.sort((a, b) => a[0] - b[0]);
return cpu_stat_array;
},
calcCPULoad(cpu_stat_array) {
let ret_array = [];
cpu_stat_array.forEach((c, i) => {
let loadUser = 0,
loadNice = 0,
loadSys = 0,
loadIdle = 0,
loadIo = 0,
loadIrq = 0,
loadSirq = 0,
loadAvg = 0;
if(this.lastStatArray !== null && this.lastStatArray.cpuNum === c.cpuNum) {
let user = c[1] - this.lastStatArray[i][1],
nice = c[2] - this.lastStatArray[i][2],
sys = c[3] - this.lastStatArray[i][3],
idle = c[4] - this.lastStatArray[i][4],
io = c[5] - this.lastStatArray[i][5],
irq = c[6] - this.lastStatArray[i][6],
sirq = c[7] - this.lastStatArray[i][7];
let sum = user + nice + sys + idle + io + irq + sirq;
loadUser = Number((100 * user / sum).toFixed(1));
loadNice = Number((100 * nice / sum).toFixed(1));
loadSys = Number((100 * sys / sum).toFixed(1));
loadIdle = Number((100 * idle / sum).toFixed(1));
loadIo = Number((100 * io / sum).toFixed(1));
loadIrq = Number((100 * irq / sum).toFixed(1));
loadSirq = Number((100 * sirq / sum).toFixed(1));
loadAvg = Math.round(100 * (user + nice + sys + io + irq + sirq) / sum);
};
ret_array.push(
{cpuNum: c[0], loadUser, loadNice, loadSys, loadIdle, loadIo, loadIrq, loadSirq, loadAvg});
});
this.lastStatArray = cpu_stat_array;
return ret_array;
},
getCPUData(cpu_data) {
return L.resolveDefault(fs.read('/proc/stat'), null).then(cpu_data => {
if(cpu_data) {
let cpu_stat_array = this.parseProcData(cpu_data);
// For single-core CPU
if(cpu_stat_array.length === 2) {
cpu_stat_array = cpu_stat_array.slice(0, 1);
};
let cpu_load_array = this.calcCPULoad(cpu_stat_array);
for(let i = 0; i < cpu_load_array.length; i++) {
let num = i;
let total = cpu_load_array[i].loadAvg;
let user = cpu_load_array[i].loadUser;
let sys = cpu_load_array[i].loadSys;
let sirq = cpu_load_array[i].loadSirq;
if(!(num in this.CPUDevices)) {
this.CPUDevices[num] = {
num : i,
name: String(cpu_load_array[i].cpuNum),
load: [],
};
};
let load_array = this.CPUDevices[num].load;
load_array.push([
new Date().getTime(),
total || 0,
user || 0,
sys || 0,
sirq || 0,
]);
if(load_array.length > this.dataBufferSize) {
load_array.shift();
};
};
};
return this.CPUDevices;
});
},
loadSVG(src) {
return request.get(src).then(response => {
if(!response.ok) {
throw new Error(response.statusText);
};
return E('div', {
'class': 'svg_background',
}, E(response.text()));
});
},
updateGraph(cpu_num, svg, peak, lines, cb) {
let G = svg.firstElementChild;
let view = document.querySelector('#view');
let width = view.offsetWidth - 2;
let height = 300 - 2;
let base_step = 5;
let time_interval = 60;
let time_interval_min = time_interval / 60
let step = base_step * this.pollInterval;
let data_wanted = Math.ceil(width / step);
let timeline_offset = width % step;
let data_values = [];
let line_elements = [];
for(let i = 0; i < lines.length; i++) {
if(lines[i] != null) {
data_values.push([]);
};
};
let info = {
line_current: [],
line_average: [],
line_peak : [],
};
/* prefill datasets */
for(let i = 0; i < data_values.length; i++) {
for(let j = 0; j < data_wanted; j++) {
data_values[i][j] = NaN;
};
};
/* plot horizontal time interval lines */
for(let i = width % (base_step * time_interval); i < width; i += base_step * time_interval) {
let x = i - (timeline_offset);
let line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x);
line.setAttribute('y1', 0);
line.setAttribute('x2', x);
line.setAttribute('y2', '100%');
line.setAttribute('class', 'grid');
let text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', x + 5);
text.setAttribute('y', 15);
text.append(document.createTextNode(String((width - i) / base_step / time_interval) + 'm'));
G.append(line);
G.append(text);
};
info.interval = this.pollInterval;
info.timeframe = Math.floor(data_wanted / time_interval * this.pollInterval);
this.graphPolls.push({
cpu_num,
svg,
peak,
lines,
cb,
info,
width,
height,
step,
data_wanted,
values : data_values,
timestamp : 0,
fill : 1,
});
},
pollData() {
poll.add(L.bind(function() {
return this.getCPUData().then(L.bind(function(datasets) {
for(let gi = 0; gi < this.graphPolls.length; gi++) {
let ctx = this.graphPolls[gi];
if(!datasets[ctx.cpu_num]) {
continue;
};
let data = datasets[ctx.cpu_num].load;
if(!data) {
continue;
};
let values = ctx.values;
let lines = ctx.lines;
let info = ctx.info;
let peak = ctx.peak;
let data_scale = 0;
let data_wanted = ctx.data_wanted;
let last_timestamp = NaN;
for(let i = 0, di = 0; di < lines.length; di++) {
if(lines[di] == null) {
continue;
};
for(let j = ctx.timestamp ? 0 : 1; j < data.length; j++) {
/* skip overlapping and empty entries */
if(data[j][0] <= ctx.timestamp) {
continue;
};
if(i == 0) {
ctx.fill++;
last_timestamp = data[j][0];
};
info.line_current[i] = data[j][di + 1];
values[i].push(info.line_current[i]);
};
i++;
};
/* cut off outdated entries */
ctx.fill = Math.min(ctx.fill, data_wanted);
for(let i = 0; i < values.length; i++) {
let len = values[i].length;
values[i] = values[i].slice(len - data_wanted, len);
/* find peaks, averages */
info.line_peak[i] = NaN;
info.line_average[i] = 0;
let nonempty = 0;
for(let j = 0; j < values[i].length; j++) {
info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]);
info.line_peak[i] = Number(info.line_peak[i].toFixed(1));
if(!isNaN(values[i][j])) {
nonempty++;
info.line_average[i] += values[i][j];
};
};
info.line_average[i] = info.line_average[i] / nonempty;
info.line_average[i] = Number(info.line_average[i].toFixed(1));
};
/* remember current timestamp, calculate horizontal scale */
if(!isNaN(last_timestamp)) {
ctx.timestamp = last_timestamp;
};
let size = Math.floor(Math.log2(info.peak));
let div = Math.pow(2, size - (size % 10));
data_scale = ctx.height / peak;
/* plot data */
for(let i = 0, di = 0; di < lines.length; di++) {
if(lines[di] == null) {
continue;
};
let el = ctx.svg.firstElementChild.getElementById(lines[di].line);
let pt = '0,' + ctx.height;
let y = 0;
if(!el) {
continue;
};
for(let j = 0; j < values[i].length; j++) {
let x = j * ctx.step;
y = ctx.height - Math.floor(values[i][j] * data_scale);
//y -= Math.floor(y % (1 / data_scale));
y = isNaN(y) ? ctx.height + 1 : y;
pt += ` ${x},${y}`;
};
pt += ` ${ctx.width},${y} ${ctx.width},${ctx.height}`;
el.setAttribute('points', pt);
i++;
};
info.label_25 = 0.25 * peak;
info.label_50 = 0.50 * peak;
info.label_75 = 0.75 * peak;
if(typeof(ctx.cb) == 'function') {
ctx.cb(ctx.svg, info);
};
};
}, this));
}, this), this.pollInterval);
},
load() {
return Promise.all([
this.loadSVG(L.resource('svg/cpu.svg')),
this.getCPUData(),
]);
},
render(data) {
let svg = data[0];
let cpus = data[1];
let map = E('div', { 'class': 'cbi-map', 'id': 'map' });
let tabs = E('div');
map.append(tabs);
for(let i of Object.values(cpus)) {
let cpu_num = i.num;
let cpu_name = (Number(i.name) >= 0 ) ? `${_('CPU')} ${i.name}` : _('Total');
let csvg = svg.cloneNode(true);
tabs.append(E('div', { 'class': 'cbi-section', 'data-tab': cpu_num, 'data-tab-title': cpu_name }, [
csvg,
E('div', { 'class': 'right' }, E('small', { 'data-graph': 'scale' }, '-')),
E('br'),
E('table', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend total' }, _('Total') + ':')),
E('td', { 'class': 'td', 'data-graph': 'total_cur' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
E('td', { 'class': 'td', 'data-graph': 'total_avg' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
E('td', { 'class': 'td', 'data-graph': 'total_peak' }, '-'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend user' }, 'User' + ':')),
E('td', { 'class': 'td', 'data-graph': 'user_cur' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
E('td', { 'class': 'td', 'data-graph': 'user_avg' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
E('td', { 'class': 'td', 'data-graph': 'user_peak' }, '-'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend sys' }, 'System' + ':')),
E('td', { 'class': 'td', 'data-graph': 'sys_cur' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
E('td', { 'class': 'td', 'data-graph': 'sys_avg' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
E('td', { 'class': 'td', 'data-graph': 'sys_peak' }, '-'),
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend sirq' }, 'Sirq' + ':')),
E('td', { 'class': 'td', 'data-graph': 'sirq_cur' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
E('td', { 'class': 'td', 'data-graph': 'sirq_avg' }, '-'),
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
E('td', { 'class': 'td', 'data-graph': 'sirq_peak' }, '-'),
]),
]),
E('br'),
]));
this.updateGraph(
cpu_num,
csvg,
100,
[ { 'line': 'total_line' }, { 'line': 'user_line' }, { 'line': 'sys_line' }, { 'line': 'sirq_line' } ],
(svg, info) => {
let G = svg.firstElementChild, tab = svg.parentNode;
G.getElementById('label_25').firstChild.data = '%d %%'.format(info.label_25);
G.getElementById('label_50').firstChild.data = '%d %%'.format(info.label_50);
G.getElementById('label_75').firstChild.data = '%d %%'.format(info.label_75);
tab.querySelector('[data-graph="scale"]').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval);
dom.content(tab.querySelector('[data-graph="total_cur"]'), info.line_current[0] + ' %');
dom.content(tab.querySelector('[data-graph="total_avg"]'), info.line_average[0] + ' %');
dom.content(tab.querySelector('[data-graph="total_peak"]'), info.line_peak[0] + ' %');
dom.content(tab.querySelector('[data-graph="user_cur"]'), info.line_current[1] + ' %');
dom.content(tab.querySelector('[data-graph="user_avg"]'), info.line_average[1] + ' %');
dom.content(tab.querySelector('[data-graph="user_peak"]'), info.line_peak[1] + ' %');
dom.content(tab.querySelector('[data-graph="sys_cur"]'), info.line_current[2] + ' %');
dom.content(tab.querySelector('[data-graph="sys_avg"]'), info.line_average[2] + ' %');
dom.content(tab.querySelector('[data-graph="sys_peak"]'), info.line_peak[2] + ' %');
dom.content(tab.querySelector('[data-graph="sirq_cur"]'), info.line_current[3] + ' %');
dom.content(tab.querySelector('[data-graph="sirq_avg"]'), info.line_average[3] + ' %');
dom.content(tab.querySelector('[data-graph="sirq_peak"]'), info.line_peak[3] + ' %');
}
);
};
ui.tabs.initTabGroup(tabs.childNodes);
this.pollData();
return E([], [
E('h2', _('CPU load')),
E('div', {'class': 'cbi-map-descr'},
_('This page displays the average CPU load at %d second interval.').format(this.pollInterval)),
map,
]);
},
handleSaveApply: null,
handleSave : null,
handleReset : null,
});
@@ -0,0 +1,353 @@
'use strict';
'require baseclass';
'require fs';
document.head.append(E('style', {'type': 'text/css'},
`
.cpu-status-view-mode-entry {
display: inline-block;
cursor: pointer;
margin: 2px 4px 2px 0 !important;
padding: 2px 4px;
border: 1px dotted;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
opacity: 0.7;
}
.cpu-status-view-mode-entry-checked {
border: 1px solid;
opacity: 1;
}
`));
return baseclass.extend({
title : _('CPU Load'),
viewName : 'cpu_status',
availableViewModes: {
0: [ 'allCPUs', _('Load') ],
1: [ 'allCPUsDetail', _('Detailed load') ],
2: [ 'eachCPU', _('Load of each CPU') ],
3: [ 'eachCPUDetail', _('Detailed load of each CPU') ],
},
viewMode : 0,
lastStatArray : null,
restoreSettingsFromLocalStorage() {
let viewModeLocal = localStorage.getItem(`luci-app-${this.viewName}-viewMode`);
if(viewModeLocal) {
this.viewMode = Number(viewModeLocal);
};
},
saveSettingsToLocalStorage(viewMode) {
if(this.viewMode != viewMode) {
localStorage.setItem(
`luci-app-${this.viewName}-viewMode`, String(viewMode));
};
},
parseProcData(data) {
let cpuStatArray = [];
let statItemsArray = data.trim().split('\n').filter(s => s.startsWith('cpu'));
for(let str of statItemsArray) {
let arr = str.split(/\s+/).slice(0, 8);
arr[0] = (arr[0] == 'cpu') ? Infinity : arr[0].replace('cpu', '');
cpuStatArray.push(arr.map(e => Number(e)));
};
cpuStatArray.sort((a, b) => a[0] - b[0]);
return cpuStatArray;
},
calcCPULoad(cpuStatArray) {
let retArray = [];
cpuStatArray.forEach((c, i) => {
let loadUser = 0,
loadNice = 0,
loadSys = 0,
loadIdle = 0,
loadIo = 0,
loadIrq = 0,
loadSirq = 0,
loadAvg = 0;
if(this.lastStatArray !== null && this.lastStatArray.cpuNum == c.cpuNum) {
let user = c[1] - this.lastStatArray[i][1],
nice = c[2] - this.lastStatArray[i][2],
sys = c[3] - this.lastStatArray[i][3],
idle = c[4] - this.lastStatArray[i][4],
io = c[5] - this.lastStatArray[i][5],
irq = c[6] - this.lastStatArray[i][6],
sirq = c[7] - this.lastStatArray[i][7];
let sum = user + nice + sys + idle + io + irq + sirq;
loadUser = Number((100 * user / sum).toFixed(1));
loadNice = Number((100 * nice / sum).toFixed(1));
loadSys = Number((100 * sys / sum).toFixed(1));
loadIdle = Number((100 * idle / sum).toFixed(1));
loadIo = Number((100 * io / sum).toFixed(1));
loadIrq = Number((100 * irq / sum).toFixed(1));
loadSirq = Number((100 * sirq / sum).toFixed(1));
loadAvg = Math.round(100 * (user + nice + sys + io + irq + sirq) / sum);
};
retArray.push(
{cpuNum: c[0], loadUser, loadNice, loadSys, loadIdle, loadIo, loadIrq, loadSirq, loadAvg});
});
return retArray;
},
allCPUs: {
makeTable() {
this.table = E('table', { 'class': 'table' });
},
append(cpuNum, cpuLoadObj, cpuLoadFlag) {
if(cpuNum == Infinity) {
this.table.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' },
_('Total load')),
E('td', { 'class': 'td' },
E('div', {
'class': 'cbi-progressbar',
'title': (cpuLoadFlag) ?
cpuLoadObj.loadAvg + '%' :
_('Calculating') + '...',
},
E('div', { 'style': 'width:' + cpuLoadObj.loadAvg + '%' })
)
),
])
);
};
},
},
allCPUsDetail: {
cpuTableTitles: [
_('All CPUs'),
_('Load'),
'user %',
'nice %',
'system %',
'idle %',
'iowait %',
'irq %',
'softirq %',
],
makeTable() {
this.table = E('table', { 'class': 'table' },
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th left' }, this.cpuTableTitles[0]),
E('th', { 'class': 'th left' }, this.cpuTableTitles[1]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[2]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[3]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[4]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[5]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[6]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[7]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[8]),
])
);
},
append(cpuNum, cpuLoadObj, cpuLoadFlag) {
if(cpuNum == Infinity) {
this.table.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': this.cpuTableTitles[0] },
_('Total load')),
E('td', { 'class': 'td left', 'data-title': this.cpuTableTitles[1] },
E('div', {
'class': 'cbi-progressbar',
'title': (cpuLoadFlag) ?
cpuLoadObj.loadAvg + '%' :
_('Calculating') + '...',
'style': 'min-width:8em !important',
},
E('div', { 'style': 'width:' + cpuLoadObj.loadAvg + '%' })
)
),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[2] }, cpuLoadObj.loadUser),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[3] }, cpuLoadObj.loadNice),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[4] }, cpuLoadObj.loadSys),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[5] }, cpuLoadObj.loadIdle),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[6] }, cpuLoadObj.loadIo),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[7] }, cpuLoadObj.loadIrq),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[8] }, cpuLoadObj.loadSirq),
])
);
};
},
},
eachCPU: {
makeTable() {
this.table = E('table', { 'class': 'table' });
},
append(cpuNum, cpuLoadObj, cpuLoadFlag) {
this.table.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' },
(cpuNum == Infinity) ? _('Total load') : _('CPU') + ' ' + cpuNum),
E('td', { 'class': 'td' },
E('div', {
'class': 'cbi-progressbar',
'title': (cpuLoadFlag) ?
cpuLoadObj.loadAvg + '%' :
_('Calculating') + '...',
},
E('div', { 'style': 'width:' + cpuLoadObj.loadAvg + '%' })
)
),
])
);
},
},
eachCPUDetail: {
cpuTableTitles: [
_('CPU'),
_('Load'),
'user %',
'nice %',
'system %',
'idle %',
'iowait %',
'irq %',
'softirq %',
],
makeTable() {
this.table = E('table', { 'class': 'table' },
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th left' }, this.cpuTableTitles[0]),
E('th', { 'class': 'th left' }, this.cpuTableTitles[1]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[2]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[3]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[4]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[5]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[6]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[7]),
E('th', { 'class': 'th center' }, this.cpuTableTitles[8]),
])
);
},
append(cpuNum, cpuLoadObj, cpuLoadFlag) {
this.table.append(
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'data-title': this.cpuTableTitles[0] },
(cpuNum == Infinity) ? _('Total load') : _('CPU') + ' ' + cpuNum),
E('td', { 'class': 'td left', 'data-title': this.cpuTableTitles[1] },
E('div', {
'class': 'cbi-progressbar',
'title': (cpuLoadFlag) ?
cpuLoadObj.loadAvg + '%' :
_('Calculating') + '...',
'style': 'min-width:8em !important',
},
E('div', { 'style': 'width:' + cpuLoadObj.loadAvg + '%' })
)
),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[2] }, cpuLoadObj.loadUser),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[3] }, cpuLoadObj.loadNice),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[4] }, cpuLoadObj.loadSys),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[5] }, cpuLoadObj.loadIdle),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[6] }, cpuLoadObj.loadIo),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[7] }, cpuLoadObj.loadIrq),
E('td', { 'class': 'td center',
'data-title': this.cpuTableTitles[8] }, cpuLoadObj.loadSirq),
])
);
},
},
makeCPUTable(cpuLoadArray) {
let currentView = (this.availableViewModes[this.viewMode] !== undefined) ?
this[this.availableViewModes[this.viewMode][0]] :
this[this.availableViewModes[0][0]];
currentView.makeTable();
cpuLoadArray.forEach((c, i) => {
currentView.append(c.cpuNum, c, (this.lastStatArray !== null));
});
return currentView.table;
},
load() {
this.restoreSettingsFromLocalStorage();
return L.resolveDefault(fs.read('/proc/stat'), null);
},
render(cpuData) {
if(!cpuData) return;
let cpuStatArray = this.parseProcData(cpuData);
// For single-core CPU
if(cpuStatArray.length == 2) {
cpuStatArray = cpuStatArray.slice(1, 2);
delete this.availableViewModes[2];
delete this.availableViewModes[3];
if(this.viewMode > 1) this.viewMode = 0;
};
let cpuLoadArray = this.calcCPULoad(cpuStatArray);
let cpuTable = this.makeCPUTable(cpuLoadArray);
let viewModeSelectFunc = value => {
this.saveSettingsToLocalStorage(value);
this.viewMode = value;
viewModeEntries.forEach(i => {
i.classList.remove('cpu-status-view-mode-entry-checked');
});
viewModeEntries[value].classList.add('cpu-status-view-mode-entry-checked');
let newTable = this.makeCPUTable(cpuLoadArray);
cpuTable.replaceWith(newTable);
cpuTable = newTable;
};
let viewModeSelectOnClick = ev => {
viewModeSelectFunc(ev.target.dataset.value);
ev.target.blur();
};
let viewModeEntries = [];
for(let [i, v] of Object.entries(this.availableViewModes)) {
viewModeEntries.push(
E('span', {
'class' : 'cpu-status-view-mode-entry',
'href' : 'javascript:void(0)',
'data-value': i,
'click' : viewModeSelectOnClick,
}, v[1])
);
};
viewModeSelectFunc(this.viewMode);
this.lastStatArray = cpuStatArray;
return E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'margin-bottom:1em' },
viewModeEntries
),
cpuTable,
]);
},
});
+58
View File
@@ -0,0 +1,58 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: ru\n"
msgid "(%d minute window, %d second interval)"
msgstr "(%d минутное окно, %d секундный интервал)"
msgid "All CPUs"
msgstr "Все ЦПУ"
msgid "Average:"
msgstr "Средняя:"
msgid "Calculating"
msgstr "Вычисление"
msgid "CPU"
msgstr "ЦПУ"
msgid "CPU Load"
msgstr "Загрузка ЦПУ"
msgid "CPU load"
msgstr "Загрузка ЦПУ"
msgid "Detailed load"
msgstr "Подробная загрузка"
msgid "Detailed load of each CPU"
msgstr "Подробная загрузка каждого ЦПУ"
msgid "Load"
msgstr "Загрузка"
msgid "Load of each CPU"
msgstr "Загрузка каждого ЦПУ"
msgid "Peak:"
msgstr "Пиковая:"
msgid "This page displays the average CPU load at %d second interval."
msgstr "На этой странице отображается средняя загрузка ЦПУ за %d секундный интервал."
msgid "Total"
msgstr "Общая"
msgid "Total load"
msgstr "Общая загрузка"
@@ -0,0 +1,47 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "(%d minute window, %d second interval)"
msgstr ""
msgid "All CPUs"
msgstr ""
msgid "Average:"
msgstr ""
msgid "Calculating"
msgstr ""
msgid "CPU"
msgstr ""
msgid "CPU Load"
msgstr ""
msgid "CPU load"
msgstr ""
msgid "Detailed load"
msgstr ""
msgid "Detailed load of each CPU"
msgstr ""
msgid "Load"
msgstr ""
msgid "Load of each CPU"
msgstr ""
msgid "Peak:"
msgstr ""
msgid "This page displays the average CPU load at %d second interval."
msgstr ""
msgid "Total"
msgstr ""
msgid "Total load"
msgstr ""
+1
View File
@@ -0,0 +1 @@
zh_Hans
@@ -0,0 +1,58 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: zh_Hans\n"
msgid "(%d minute window, %d second interval)"
msgstr "(最近 %d 分钟信息,每 %d 秒刷新)"
msgid "All CPUs"
msgstr "所有 CPU"
msgid "Average:"
msgstr "平均:"
msgid "Calculating"
msgstr "计算中..."
msgid "CPU"
msgstr "CPU"
msgid "CPU Load"
msgstr "CPU"
msgid "CPU load"
msgstr "CPU 负载"
msgid "Detailed load"
msgstr "负载详情"
msgid "Detailed load of each CPU"
msgstr "各 CPU 负载详情"
msgid "Load"
msgstr "负载"
msgid "Load of each CPU"
msgstr "各 CPU 负载"
msgid "Peak:"
msgstr "峰值:"
msgid "This page displays the average CPU load at %d second interval."
msgstr "此页面展示 %d 秒内 CPU 平均负载。"
msgid "Total"
msgstr "总计"
msgid "Total load"
msgstr "总负载"
@@ -0,0 +1,40 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: zh_Hant\n"
msgid "All CPUs"
msgstr "所有 CPUs"
msgid "Calculating"
msgstr "計算中"
msgid "CPU"
msgstr "CPU"
msgid "CPU Load"
msgstr "CPU 負載"
msgid "Detailed load"
msgstr "詳細負載"
msgid "Detailed load of each CPU"
msgstr "詳細負載 (每個 CPU)"
msgid "Load"
msgstr "負載"
msgid "Load of each CPU"
msgstr "負載 (每個 CPU)"
msgid "Total load"
msgstr "總負載"
@@ -0,0 +1,13 @@
{
"admin/status/realtime/cpu": {
"title": "CPU",
"order": 9,
"action": {
"type": "view",
"path": "status/cpu"
},
"depends": {
"acl": [ "luci-app-cpu-status" ]
}
}
}
@@ -0,0 +1,10 @@
{
"luci-app-cpu-status": {
"description": "Grant access to cpu-status procedures",
"read": {
"file": {
"/proc/stat": [ "read" ]
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB