mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-29 23:05:13 +08:00
update 2026-09-26 11:07:27
This commit is contained in:
1 parent
7b88b273d0
commit
97ae9f68da
4898 files changed
-1712274
No files matched your search
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,20 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2026 gSpot (https://github.com/gSpotx2f/luci-app-temp-status)
|
||||
#
|
||||
# This is free software, licensed under the MIT License.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-temp-status
|
||||
PKG_VERSION:=0.8.1
|
||||
PKG_RELEASE:=1
|
||||
LUCI_TITLE:=Temperature sensors data for the LuCI status page
|
||||
LUCI_DEPENDS:=+ucode +ucode-mod-fs
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
#include ../../luci.mk
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -1,38 +0,0 @@
|
||||
# luci-app-temp-status
|
||||
Temperature sensors data for the LuCI status page (OpenWrt webUI).
|
||||
|
||||
OpenWrt >= 22.03.
|
||||
|
||||
**Dependences:** ucode, ucode-mod-fs.
|
||||
|
||||
## Installation notes
|
||||
|
||||
**OpenWrt >= 25.12:**
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-app-temp-status-0.8.1-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-temp-status-0.8.1-r1.apk
|
||||
apk --allow-untrusted add /tmp/luci-app-temp-status-0.8.1-r1.apk
|
||||
rm /tmp/luci-app-temp-status-0.8.1-r1.apk
|
||||
service rpcd restart
|
||||
|
||||
i18n-ru:
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-i18n-temp-status-ru-0.8.1-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-temp-status-ru-0.8.1-r1.apk
|
||||
apk --allow-untrusted add /tmp/luci-i18n-temp-status-ru-0.8.1-r1.apk
|
||||
rm /tmp/luci-i18n-temp-status-ru-0.8.1-r1.apk
|
||||
|
||||
**OpenWrt <= 24.10:**
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-app-temp-status_0.8.1-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-temp-status_0.8.1-r1_all.ipk
|
||||
opkg install /tmp/luci-app-temp-status_0.8.1-r1_all.ipk
|
||||
rm /tmp/luci-app-temp-status_0.8.1-r1_all.ipk
|
||||
service rpcd restart
|
||||
|
||||
i18n-ru:
|
||||
|
||||
wget --no-check-certificate -O /tmp/luci-i18n-temp-status-ru_0.8.1-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-temp-status-ru_0.8.1-r1_all.ipk
|
||||
opkg install /tmp/luci-i18n-temp-status-ru_0.8.1-r1_all.ipk
|
||||
rm /tmp/luci-i18n-temp-status-ru_0.8.1-r1_all.ipk
|
||||
|
||||
## Screenshots:
|
||||
|
||||

|
||||
@@ -1,18 +0,0 @@
|
||||
<?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="temp_line" class="temp" 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>
|
||||
|
||||
<line id="hot_line" class="hot" x1="0" y1="-1" x2="100%" y2="-1" />
|
||||
<line id="overheat_line" class="overheat" x1="0" y1="-1" x2="100%" y2="-1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 740 B |
-470
@@ -1,470 +0,0 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require rpc';
|
||||
|
||||
document.head.append(E('style', {'type': 'text/css'},
|
||||
`
|
||||
:root {
|
||||
--app-temp-status-font-color: #2e2e2e;
|
||||
--app-temp-status-border-color: var(--border-color-medium, #d4d4d4);
|
||||
--app-temp-status-hot-color: #fff7e2;
|
||||
--app-temp-status-overheat-color: #ffe9e8;
|
||||
}
|
||||
:root[data-darkmode="true"] {
|
||||
--app-temp-status-font-color: #fff;
|
||||
--app-temp-status-border-color: var(--border-color-medium, #444);
|
||||
--app-temp-status-hot-color: #8d7000;
|
||||
--app-temp-status-overheat-color: #a93734;
|
||||
}
|
||||
.temp-status-hot {
|
||||
background-color: var(--app-temp-status-hot-color) !important;
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
.temp-status-hot .td {
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
.temp-status-hot td {
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
.temp-status-overheat {
|
||||
background-color: var(--app-temp-status-overheat-color) !important;
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
.temp-status-overheat .td {
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
.temp-status-overheat td {
|
||||
color: var(--app-temp-status-font-color) !important;
|
||||
}
|
||||
|
||||
.temp-status-temp-area {
|
||||
width: 100%;
|
||||
padding: 0 0 1em 0;
|
||||
display: flex;
|
||||
-webkit-align-items: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-justify-content: left;
|
||||
justify-content: left;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.temp-status-list-item {
|
||||
display: inline-block;
|
||||
flex-grow: 0;
|
||||
-webkit-flex-grow 0;
|
||||
flex-shrink: 1;
|
||||
-webkit-flex-shrink 1;
|
||||
width: 29em;
|
||||
word-wrap: break-word;
|
||||
margin: 0px 4px 4px 0 !important;
|
||||
padding: 4px;
|
||||
border: 1px solid;
|
||||
border-color: transparent;
|
||||
border-color: var(--app-temp-status-border-color);
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.temp-status-temp-value {
|
||||
display: inline-block;
|
||||
margin-left: 0.5em !important;
|
||||
width: 4em;
|
||||
text-align: left;
|
||||
}
|
||||
.temp-status-sensor-name {
|
||||
display: inline-block;
|
||||
padding-left: 1.5em;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
#temp-status-buttons-wrapper {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.temp-status-button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin: 2px 4px 2px 0 !important;
|
||||
padding: 3px 5px;
|
||||
border: 1px solid;
|
||||
border-color: transparent;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.7;
|
||||
background-color: rgba(100 100 100 / 0.2);
|
||||
}
|
||||
.temp-status-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.temp-status-button:active {
|
||||
opacity: 1.0;
|
||||
}
|
||||
.temp-status-hide-item {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin: 0 0.5em 0 0 !important;
|
||||
padding: 1px 5px;
|
||||
border: 1px dotted;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.6;
|
||||
background-color: rgba(100 100 100 / 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
.temp-status-hide-item:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.temp-status-hide-item:active {
|
||||
opacity: 1.0;
|
||||
}
|
||||
`));
|
||||
|
||||
return baseclass.extend({
|
||||
title : _('Temperature'),
|
||||
|
||||
viewName : 'temp-status',
|
||||
|
||||
tempHot : 95,
|
||||
|
||||
tempOverheat : 105,
|
||||
|
||||
sensorsData : null,
|
||||
|
||||
tempData : null,
|
||||
|
||||
sensorsPath : [],
|
||||
|
||||
hiddenItems : new Set(),
|
||||
|
||||
hiddenNum : E('span', {}),
|
||||
|
||||
tempTable : E('table', { 'class': 'table' }),
|
||||
|
||||
tempArea : E('div', { 'class': 'temp-status-temp-area' }),
|
||||
|
||||
tempView : E('div', {}),
|
||||
|
||||
viewType : 'table',
|
||||
|
||||
callSensors : rpc.declare({
|
||||
object: 'luci.temp-status',
|
||||
method: 'getSensors',
|
||||
expect: { '': {} },
|
||||
}),
|
||||
|
||||
callTempData: rpc.declare({
|
||||
object: 'luci.temp-status',
|
||||
method: 'getTempData',
|
||||
params: [ 'tpaths' ],
|
||||
expect: { '': {} },
|
||||
}),
|
||||
|
||||
formatTemp(mc) {
|
||||
return Number((mc / 1000).toFixed(1));
|
||||
},
|
||||
|
||||
sortFunc(a, b) {
|
||||
return (a.number > b.number) ? 1 : (a.number < b.number) ? -1 : 0;
|
||||
},
|
||||
|
||||
restoreSettingsFromLocalStorage() {
|
||||
let hiddenItems = localStorage.getItem(`luci-app-${this.viewName}-hiddenItems`);
|
||||
if(hiddenItems) {
|
||||
this.hiddenItems = new Set(hiddenItems.split(','));
|
||||
};
|
||||
let view = localStorage.getItem(`luci-app-${this.viewName}-view`);
|
||||
if(view) {
|
||||
this.viewType = view;
|
||||
};
|
||||
},
|
||||
|
||||
saveSettingsToLocalStorage() {
|
||||
localStorage.setItem(
|
||||
`luci-app-${this.viewName}-hiddenItems`, Array.from(this.hiddenItems).join(','));
|
||||
localStorage.setItem(
|
||||
`luci-app-${this.viewName}-view`, this.viewType);
|
||||
},
|
||||
|
||||
makeTempTableContent() {
|
||||
this.tempTable.innerHTML = '';
|
||||
this.tempTable.append(
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th left', 'width': '33%' }, _('Sensor')),
|
||||
E('th', { 'class': 'th left' }, _('Temperature')),
|
||||
E('th', { 'class': 'th right', 'width': '1%' }, ' '),
|
||||
])
|
||||
);
|
||||
|
||||
if(this.sensorsData && this.tempData) {
|
||||
for(let [k, v] of Object.entries(this.sensorsData)) {
|
||||
v.sort(this.sortFunc);
|
||||
|
||||
for(let i of Object.values(v)) {
|
||||
let sensor = i.title || i.item;
|
||||
|
||||
if(i.sources === undefined) {
|
||||
continue;
|
||||
};
|
||||
|
||||
i.sources.sort(this.sortFunc);
|
||||
|
||||
for(let j of i.sources) {
|
||||
if(this.hiddenItems.has(j.path)) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let temp = this.tempData[j.path];
|
||||
let name = (j.label !== undefined) ? sensor + " / " + j.label :
|
||||
(j.item !== undefined) ? sensor + " / " + j.item.replace(/_input$/, "") : sensor
|
||||
|
||||
if(temp !== undefined && temp !== null) {
|
||||
temp = this.formatTemp(temp);
|
||||
};
|
||||
|
||||
let tempHot = NaN;
|
||||
let tempOverheat = NaN;
|
||||
let tpoints = j.tpoints;
|
||||
let tpointsString = '';
|
||||
|
||||
if(tpoints) {
|
||||
for(let i of Object.values(tpoints)) {
|
||||
let t = this.formatTemp(i.temp);
|
||||
tpointsString += ` ${i.type}: ${t} °C`;
|
||||
|
||||
if(i.type == 'max' || i.type == 'critical' || i.type == 'emergency') {
|
||||
if(!(tempOverheat <= t)) {
|
||||
tempOverheat = t;
|
||||
};
|
||||
}
|
||||
else if(i.type == 'hot') {
|
||||
tempHot = t;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if(isNaN(tempHot) && isNaN(tempOverheat)) {
|
||||
tempHot = this.tempHot;
|
||||
tempOverheat = this.tempOverheat;
|
||||
};
|
||||
|
||||
let rowStyle = (temp >= tempOverheat) ? ' temp-status-overheat':
|
||||
(temp >= tempHot) ? ' temp-status-hot' : '';
|
||||
|
||||
this.tempTable.append(
|
||||
E('tr', {
|
||||
'class' : 'tr' + rowStyle,
|
||||
'data-path': j.path ,
|
||||
}, [
|
||||
E('td', {
|
||||
'class' : 'td left',
|
||||
'data-title': _('Sensor')
|
||||
},
|
||||
(tpointsString.length > 0) ?
|
||||
`<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${tpointsString}">${name}</span>` :
|
||||
name
|
||||
),
|
||||
E('td', {
|
||||
'class' : 'td left',
|
||||
'data-title': _('Temperature')
|
||||
},
|
||||
(temp === undefined || temp === null) ? '-' : temp + ' °C'
|
||||
),
|
||||
E('td', {
|
||||
'class' : 'td right',
|
||||
'data-title': _('Hide'),
|
||||
'title' : _('Hide'),
|
||||
},
|
||||
E('span', {
|
||||
'class': 'temp-status-hide-item',
|
||||
'title': _('Hide'),
|
||||
'click': () => this.hideItem(j.path),
|
||||
}, 'Χ'),
|
||||
),
|
||||
])
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if(this.tempTable.childNodes.length == 1) {
|
||||
this.tempTable.append(
|
||||
E('tr', { 'class': 'tr placeholder' },
|
||||
E('td', { 'class': 'td' },
|
||||
E('em', {}, _('No temperature sensors available'))
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return this.tempTable;
|
||||
},
|
||||
|
||||
makeTempAreaContent() {
|
||||
this.tempArea.innerHTML = '';
|
||||
|
||||
for(let [k, v] of Object.entries(this.sensorsData)) {
|
||||
v.sort(this.sortFunc);
|
||||
|
||||
for(let i of Object.values(v)) {
|
||||
let sensor = i.title || i.item;
|
||||
|
||||
if(i.sources === undefined) {
|
||||
continue;
|
||||
};
|
||||
|
||||
i.sources.sort(this.sortFunc);
|
||||
|
||||
for(let j of i.sources) {
|
||||
if(this.hiddenItems.has(j.path)) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let temp = this.tempData[j.path];
|
||||
let name = (j.label !== undefined) ? sensor + " / " + j.label :
|
||||
(j.item !== undefined) ? sensor + " / " + j.item.replace(/_input$/, "") : sensor
|
||||
|
||||
if(temp !== undefined && temp !== null) {
|
||||
temp = this.formatTemp(temp);
|
||||
};
|
||||
|
||||
let tempHot = NaN;
|
||||
let tempOverheat = NaN;
|
||||
let tpoints = j.tpoints;
|
||||
let tpointsString = '';
|
||||
|
||||
if(tpoints) {
|
||||
for(let i of Object.values(tpoints)) {
|
||||
let t = this.formatTemp(i.temp);
|
||||
tpointsString += ` ${i.type}: ${t} °C`;
|
||||
|
||||
if(i.type == 'max' || i.type == 'critical' || i.type == 'emergency') {
|
||||
if(!(tempOverheat <= t)) {
|
||||
tempOverheat = t;
|
||||
};
|
||||
}
|
||||
else if(i.type == 'hot') {
|
||||
tempHot = t;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if(isNaN(tempHot) && isNaN(tempOverheat)) {
|
||||
tempHot = this.tempHot;
|
||||
tempOverheat = this.tempOverheat;
|
||||
};
|
||||
|
||||
let itemStyle = (temp >= tempOverheat) ? ' temp-status-overheat':
|
||||
(temp >= tempHot) ? ' temp-status-hot' : '';
|
||||
|
||||
this.tempArea.append(
|
||||
E('div', { 'class': 'temp-status-list-item' + itemStyle }, [
|
||||
E('span', {
|
||||
'class': 'temp-status-hide-item',
|
||||
'title': _('Hide'),
|
||||
'click': () => this.hideItem(j.path),
|
||||
}, 'Χ'),
|
||||
E('span', {
|
||||
'class': 'temp-status-temp-value',
|
||||
}, (temp === undefined || temp === null) ? '-' : temp + ' °C'),
|
||||
E('span', {
|
||||
'class': 'temp-status-sensor-name'
|
||||
}, (tpointsString.length > 0) ?
|
||||
`<span style="cursor:help; border-bottom:1px dotted" data-tooltip="${tpointsString}">${name}</span>` :
|
||||
name
|
||||
),
|
||||
])
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if(this.tempArea.childNodes.length == 0) {
|
||||
this.tempArea.append(
|
||||
E('em', {}, _('No temperature sensors available'))
|
||||
);
|
||||
};
|
||||
|
||||
return this.tempArea;
|
||||
},
|
||||
|
||||
makeViewContent() {
|
||||
this.tempView.innerHTML = '';
|
||||
this.tempView.append((this.viewType == 'list') ? this.makeTempAreaContent() : this.makeTempTableContent());
|
||||
this.hiddenNum.textContent = this.hiddenItems.size;
|
||||
let unhide = document.getElementById('temp-status-unhide-all');
|
||||
if(unhide) {
|
||||
unhide.style.display = (this.hiddenItems.size > 0) ? 'inline-block' : 'none';
|
||||
};
|
||||
},
|
||||
|
||||
hideItem(path) {
|
||||
this.hiddenItems.add(path);
|
||||
this.saveSettingsToLocalStorage();
|
||||
this.makeViewContent();
|
||||
},
|
||||
|
||||
unhideAllItems() {
|
||||
this.hiddenItems.clear();
|
||||
this.saveSettingsToLocalStorage();
|
||||
this.makeViewContent();
|
||||
},
|
||||
|
||||
switchView() {
|
||||
this.tempView.innerHTML = '';
|
||||
this.viewType = (this.viewType == 'list') ? 'table' : 'list'
|
||||
this.saveSettingsToLocalStorage();
|
||||
this.makeViewContent();
|
||||
},
|
||||
|
||||
load() {
|
||||
this.restoreSettingsFromLocalStorage();
|
||||
if(this.sensorsData) {
|
||||
return (this.sensorsPath.length > 0) ?
|
||||
L.resolveDefault(this.callTempData(this.sensorsPath), null) :
|
||||
Promise.resolve(null);
|
||||
} else {
|
||||
return L.resolveDefault(this.callSensors(), null);
|
||||
};
|
||||
},
|
||||
|
||||
render(data) {
|
||||
if(data) {
|
||||
if(!this.sensorsData) {
|
||||
this.sensorsData = data.sensors;
|
||||
this.sensorsPath = data.temp && new Array(...Object.keys(data.temp));
|
||||
};
|
||||
this.tempData = data.temp;
|
||||
};
|
||||
|
||||
if(!this.sensorsData || !this.tempData) {
|
||||
return;
|
||||
};
|
||||
|
||||
this.makeViewContent();
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'id': 'temp-status-buttons-wrapper' }, [
|
||||
E('span', {
|
||||
'class': 'temp-status-button',
|
||||
'click': () => this.switchView(),
|
||||
}, _('Switch view')),
|
||||
E('span', {
|
||||
'id' : 'temp-status-unhide-all',
|
||||
'class': 'temp-status-button',
|
||||
'style': `display:${(this.hiddenItems.size > 0) ? 'inline-block' : 'none'}`,
|
||||
'click': () => this.unhideAllItems(),
|
||||
}, [
|
||||
_('Show hidden sensors'),
|
||||
' (',
|
||||
this.hiddenNum,
|
||||
')',
|
||||
])
|
||||
]),
|
||||
this.tempView,
|
||||
]);
|
||||
},
|
||||
});
|
||||
@@ -1,582 +0,0 @@
|
||||
'use strict';
|
||||
'require dom';
|
||||
'require poll';
|
||||
'require request';
|
||||
'require rpc';
|
||||
'require view';
|
||||
'require ui';
|
||||
|
||||
document.head.append(E('style', {'type': 'text/css'},
|
||||
`
|
||||
:root {
|
||||
--app-temp-status-temp: #147aff;
|
||||
--app-temp-status-hot: orange;
|
||||
--app-temp-status-overheat: red;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.temp {
|
||||
border-color: var(--app-temp-status-temp);
|
||||
}
|
||||
.hot {
|
||||
border-color: var(--app-temp-status-hot);
|
||||
}
|
||||
.overheat {
|
||||
border-color: var(--app-temp-status-overheat);
|
||||
}
|
||||
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 #temp_line {
|
||||
fill: var(--app-temp-status-temp);
|
||||
fill-opacity: 0.4;
|
||||
stroke: var(--app-temp-status-temp);
|
||||
stroke-width: 1;
|
||||
}
|
||||
svg #hot_line {
|
||||
stroke: var(--app-temp-status-hot);
|
||||
stroke-width: 1;
|
||||
}
|
||||
svg #overheat_line {
|
||||
stroke: var(--app-temp-status-overheat);
|
||||
stroke-width: 1;
|
||||
}
|
||||
`));
|
||||
|
||||
Math.log2 = Math.log2 || (x => Math.log(x) * Math.LOG2E);
|
||||
|
||||
return view.extend({
|
||||
tempHot : 95,
|
||||
|
||||
tempOverheat : 105,
|
||||
|
||||
pollInterval : 3,
|
||||
|
||||
tempBufferSize: 4,
|
||||
|
||||
sensorsData : null,
|
||||
|
||||
sensorsPath : [],
|
||||
|
||||
tempSources : {},
|
||||
|
||||
graphPolls : [],
|
||||
|
||||
callSensors : rpc.declare({
|
||||
object: 'luci.temp-status',
|
||||
method: 'getSensors',
|
||||
expect: { '': {} },
|
||||
}),
|
||||
|
||||
callTempData : rpc.declare({
|
||||
object: 'luci.temp-status',
|
||||
method: 'getTempData',
|
||||
params: [ 'tpaths' ],
|
||||
expect: { '': {} },
|
||||
}),
|
||||
|
||||
formatTemp(mc) {
|
||||
return Number((mc / 1000).toFixed(1));
|
||||
},
|
||||
|
||||
sortFunc(a, b) {
|
||||
return (a.number > b.number) ? 1 : (a.number < b.number) ? -1 : 0;
|
||||
},
|
||||
|
||||
getSensorsData() {
|
||||
return this.callSensors().then(data => {
|
||||
if(data) {
|
||||
this.sensorsData = data.sensors;
|
||||
this.sensorsPath = new Array(...Object.keys(data.temp));
|
||||
let tempData = data.temp;
|
||||
if(this.sensorsData && tempData) {
|
||||
for(let e of Object.values(this.sensorsData)) {
|
||||
e.sort(this.sortFunc);
|
||||
|
||||
for(let i of Object.values(e)) {
|
||||
let sensor = i.title || i.item;
|
||||
|
||||
if(i.sources === undefined) {
|
||||
continue;
|
||||
};
|
||||
|
||||
i.sources.sort(this.sortFunc);
|
||||
|
||||
for(let j of i.sources) {
|
||||
let path = j.path;
|
||||
let temp = tempData[path];
|
||||
let name = (j.label !== undefined) ? sensor + " / " + j.label :
|
||||
(j.item !== undefined) ? sensor + " / " + j.item.replace(/_input$/, "") : sensor
|
||||
|
||||
if(temp !== undefined && temp !== null) {
|
||||
temp = this.formatTemp(temp);
|
||||
};
|
||||
|
||||
let temp_hot = NaN;
|
||||
let temp_overheat = NaN;
|
||||
let tpoints = j.tpoints;
|
||||
|
||||
if(tpoints) {
|
||||
for(let i of Object.values(tpoints)) {
|
||||
let t = this.formatTemp(i.temp);
|
||||
if(i.type === 'max' || i.type === 'critical' || i.type === 'emergency') {
|
||||
if(!(temp_overheat <= t)) {
|
||||
temp_overheat = t;
|
||||
};
|
||||
}
|
||||
else if(i.type === 'hot') {
|
||||
temp_hot = t;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if(isNaN(temp_hot) && isNaN(temp_overheat)) {
|
||||
temp_hot = this.tempHot;
|
||||
temp_overheat = this.tempOverheat;
|
||||
};
|
||||
|
||||
if(!(path in this.tempSources)) {
|
||||
this.tempSources[path] = {
|
||||
name,
|
||||
path,
|
||||
temp: [ [ new Date().getTime(), temp || 0 ] ],
|
||||
temp_hot,
|
||||
temp_overheat,
|
||||
tpoints,
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return this.tempSources;
|
||||
});
|
||||
},
|
||||
|
||||
getTempData() {
|
||||
return this.callTempData(this.sensorsPath).then(data => {
|
||||
if(data) {
|
||||
let tempData = data.temp;
|
||||
if(this.sensorsData && tempData) {
|
||||
for(let [path, temp] of Object.entries(tempData)) {
|
||||
if(path in this.tempSources) {
|
||||
if(temp !== undefined && temp !== null) {
|
||||
temp = this.formatTemp(temp);
|
||||
};
|
||||
let temp_array = this.tempSources[path].temp;
|
||||
temp_array.push([ new Date().getTime(), temp || 0 ]);
|
||||
if(temp_array.length > this.tempBufferSize) {
|
||||
temp_array.shift();
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return this.tempSources;
|
||||
});
|
||||
},
|
||||
|
||||
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(tpath, svg, y_peaks, 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 : [],
|
||||
line_min : [],
|
||||
hot_line : svg.firstElementChild.getElementById('hot_line'),
|
||||
overheat_line: svg.firstElementChild.getElementById('overheat_line'),
|
||||
};
|
||||
|
||||
/* 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({
|
||||
tpath,
|
||||
svg,
|
||||
y_peaks,
|
||||
lines,
|
||||
cb,
|
||||
info,
|
||||
width,
|
||||
height,
|
||||
step,
|
||||
data_wanted,
|
||||
values : data_values,
|
||||
timestamp : 0,
|
||||
fill : 1,
|
||||
});
|
||||
},
|
||||
|
||||
pollData() {
|
||||
poll.add(L.bind(function() {
|
||||
return this.getTempData().then(L.bind(function(datasets) {
|
||||
for(let gi = 0; gi < this.graphPolls.length; gi++) {
|
||||
let ctx = this.graphPolls[gi];
|
||||
|
||||
if(!datasets[ctx.tpath]) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = datasets[ctx.tpath].temp;
|
||||
|
||||
if(!data) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let values = ctx.values;
|
||||
let lines = ctx.lines;
|
||||
let info = ctx.info;
|
||||
let y_peaks = ctx.y_peaks;
|
||||
let temp_hot = datasets[ctx.tpath].temp_hot;
|
||||
let temp_overheat = datasets[ctx.tpath].temp_overheat;
|
||||
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;
|
||||
info.line_min[i] = NaN;
|
||||
|
||||
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));
|
||||
info.line_min[i] = isNaN(info.line_min[i]) ? values[i][j] : Math.min(info.line_min[i], values[i][j]);
|
||||
info.line_min[i] = Number(info.line_min[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));
|
||||
};
|
||||
|
||||
info.peak = Math.max.apply(Math, info.line_peak);
|
||||
|
||||
/* 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));
|
||||
|
||||
if(y_peaks) {
|
||||
info.peak = (info.peak > y_peaks.t2) ? y_peaks.t2 + y_peaks.incr :
|
||||
((info.peak > y_peaks.t1) ? y_peaks.t1 + y_peaks.incr : y_peaks.t1);
|
||||
} else {
|
||||
let mult = info.peak / div;
|
||||
mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000));
|
||||
info.peak = info.peak + (mult * div) - (info.peak % (mult * div));
|
||||
};
|
||||
|
||||
data_scale = ctx.height / info.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 = isNaN(y) ? ctx.height + 1 : y;
|
||||
pt += ` ${x},${y}`;
|
||||
};
|
||||
|
||||
pt += ` ${ctx.width},${y} ${ctx.width},${ctx.height}`;
|
||||
el.setAttribute('points', pt);
|
||||
|
||||
i++;
|
||||
};
|
||||
|
||||
/* hot line y */
|
||||
if(!isNaN(temp_hot)) {
|
||||
info.hot_line.setAttribute(
|
||||
'y1', ctx.height - Math.floor(temp_hot * data_scale));
|
||||
info.hot_line.setAttribute(
|
||||
'y2', ctx.height - Math.floor(temp_hot * data_scale));
|
||||
};
|
||||
|
||||
/* overheat line y */
|
||||
if(!isNaN(temp_overheat)) {
|
||||
info.overheat_line.setAttribute(
|
||||
'y1', ctx.height - Math.floor(temp_overheat * data_scale));
|
||||
info.overheat_line.setAttribute(
|
||||
'y2', ctx.height - Math.floor(temp_overheat * data_scale));
|
||||
};
|
||||
|
||||
info.label_25 = 0.25 * info.peak;
|
||||
info.label_50 = 0.50 * info.peak;
|
||||
info.label_75 = 0.75 * info.peak;
|
||||
|
||||
if(typeof(ctx.cb) == 'function') {
|
||||
ctx.cb(ctx.svg, info);
|
||||
};
|
||||
};
|
||||
}, this));
|
||||
}, this), this.pollInterval);
|
||||
},
|
||||
|
||||
load() {
|
||||
return Promise.all([
|
||||
this.loadSVG(L.resource('svg/temperature.svg')),
|
||||
this.getSensorsData(),
|
||||
]);
|
||||
},
|
||||
|
||||
render(data) {
|
||||
let svg = data[0];
|
||||
let tsources = data[1];
|
||||
let map = E('div', { 'class': 'cbi-map', 'id': 'map' });
|
||||
|
||||
if(!tsources || Object.keys(tsources).length == 0) {
|
||||
map.append(E('div', { 'class': 'cbi-section' },
|
||||
E('div', { 'class': 'cbi-section-node' },
|
||||
E('div', { 'class': 'cbi-value' },
|
||||
E('em', {}, _('No temperature sensors available'))
|
||||
)
|
||||
)
|
||||
));
|
||||
} else {
|
||||
let tabs = E('div');
|
||||
map.append(tabs);
|
||||
|
||||
for(let i of Object.values(tsources)) {
|
||||
let tsource_name = i.name;
|
||||
let tsource_path = i.path;
|
||||
let tsource_hot = i.temp_hot;
|
||||
let tsource_overheat = i.temp_overheat;
|
||||
let tsource_tpoints = i.tpoints;
|
||||
|
||||
if(!tsource_name || !tsource_path) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let csvg = svg.cloneNode(true);
|
||||
let tpoints_section = null;
|
||||
|
||||
if(tsource_tpoints) {
|
||||
tpoints_section = E('div', { 'class': 'cbi-section-node' })
|
||||
let tpoints_table = E('table', { 'class': 'table' });
|
||||
tpoints_section.append(tpoints_table);
|
||||
|
||||
for(let i of Object.values(tsource_tpoints)) {
|
||||
tpoints_table.append(
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, i.type),
|
||||
E('td', { 'class': 'td left' }, this.formatTemp(i.temp) + ' °C' ),
|
||||
])
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
tabs.append(E('div', { 'class': 'cbi-section', 'data-tab': tsource_path, 'data-tab-title': tsource_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 temp' }, _('Temperature') + ':')),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_cur' }, '-'),
|
||||
|
||||
E('td', { 'class': 'td right top' }, E('strong', {}, _('Minimum:'))),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_min' }, '-'),
|
||||
|
||||
E('td', { 'class': 'td right top' }, E('strong', {}, _('Average:'))),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_avg' }, '-'),
|
||||
|
||||
E('td', { 'class': 'td right top' }, E('strong', {}, _('Peak:'))),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_peak' }, '-'),
|
||||
]),
|
||||
(!isNaN(tsource_hot) ?
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend hot' }, _('Hot:'))),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_hot' }, tsource_hot + ' °C'),
|
||||
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
]) : ''
|
||||
),
|
||||
(!isNaN(tsource_overheat) ?
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td right top' }, E('strong', { 'class': 'graph_legend overheat' }, _('Overheat:'))),
|
||||
E('td', { 'class': 'td', 'data-graph': 'temp_overheat' }, tsource_overheat + ' °C'),
|
||||
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
E('td', { 'class': 'td right top' }),
|
||||
]) : ''
|
||||
),
|
||||
]),
|
||||
E('br'),
|
||||
tpoints_section || '',
|
||||
E('br'),
|
||||
]));
|
||||
|
||||
this.updateGraph(
|
||||
tsource_path,
|
||||
csvg,
|
||||
{ 't1': 120, 't2': 180, 'incr': 60 },
|
||||
[ { 'line': 'temp_line' } ],
|
||||
(svg, info) => {
|
||||
let G = svg.firstElementChild, tab = svg.parentNode;
|
||||
|
||||
G.getElementById('label_25').firstChild.data = '%d °C'.format(info.label_25);
|
||||
G.getElementById('label_50').firstChild.data = '%d °C'.format(info.label_50);
|
||||
G.getElementById('label_75').firstChild.data = '%d °C'.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="temp_cur"]'), info.line_current[0] + ' °C');
|
||||
dom.content(tab.querySelector('[data-graph="temp_min"]'), info.line_min[0] + ' °C');
|
||||
dom.content(tab.querySelector('[data-graph="temp_avg"]'), info.line_average[0] + ' °C');
|
||||
dom.content(tab.querySelector('[data-graph="temp_peak"]'), info.line_peak[0] + ' °C');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ui.tabs.initTabGroup(tabs.childNodes);
|
||||
this.pollData();
|
||||
};
|
||||
|
||||
return E([], [
|
||||
E('h2', _('Temperature')),
|
||||
E('div', {'class': 'cbi-map-descr'}, _('This page displays the temperature sensors data.')),
|
||||
map,
|
||||
]);
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave : null,
|
||||
handleReset : null,
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: DeciBelioS\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.5.1\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
msgid "(%d minute window, %d second interval)"
|
||||
msgstr "(ventana de %d minutos, intervalo de %d segundos)"
|
||||
|
||||
msgid "Average:"
|
||||
msgstr "Promedio:"
|
||||
|
||||
msgid "Critical:"
|
||||
msgstr "Crítico:"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "Ocultar"
|
||||
|
||||
msgid "Hot:"
|
||||
msgstr "Hot:"
|
||||
|
||||
msgid "Minimum:"
|
||||
msgstr "Mínimo:"
|
||||
|
||||
msgid "No temperature sensors available"
|
||||
msgstr "No hay sensores de temperatura disponibles"
|
||||
|
||||
msgid "Peak:"
|
||||
msgstr "Máximo:"
|
||||
|
||||
msgid "Sensor"
|
||||
msgstr "Sensor"
|
||||
|
||||
msgid "Show hidden sensors"
|
||||
msgstr "Mostrar sensores ocultos"
|
||||
|
||||
msgid "Temperature"
|
||||
msgstr "Temperatura"
|
||||
|
||||
msgid "This page displays the temperature sensors data."
|
||||
msgstr "Esta página muestra los datos de los sensores de temperatura."
|
||||
@@ -1,52 +0,0 @@
|
||||
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 "Average:"
|
||||
msgstr "Средняя:"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "Скрыть"
|
||||
|
||||
msgid "Hot:"
|
||||
msgstr "Горячая:"
|
||||
|
||||
msgid "Minimum:"
|
||||
msgstr "Минимальная:"
|
||||
|
||||
msgid "No temperature sensors available"
|
||||
msgstr "Нет доступных датчиков температуры"
|
||||
|
||||
msgid "Overheat:"
|
||||
msgstr "Перегрев:"
|
||||
|
||||
msgid "Peak:"
|
||||
msgstr "Пиковая:"
|
||||
|
||||
msgid "Sensor"
|
||||
msgstr "Датчик"
|
||||
|
||||
msgid "Show hidden sensors"
|
||||
msgstr "Показать скрытые датчики"
|
||||
|
||||
msgid "Switch view"
|
||||
msgstr "Переключить вид"
|
||||
|
||||
msgid "Temperature"
|
||||
msgstr "Температура"
|
||||
|
||||
msgid "This page displays the temperature sensors data."
|
||||
msgstr "На этой странице отображаются данные датчиков температуры."
|
||||
@@ -1,42 +0,0 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
msgid "(%d minute window, %d second interval)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Average:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hot:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimum:"
|
||||
msgstr ""
|
||||
|
||||
msgid "No temperature sensors available"
|
||||
msgstr ""
|
||||
|
||||
msgid "Overheat:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Peak:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sensor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show hidden sensors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch view"
|
||||
msgstr ""
|
||||
|
||||
msgid "Temperature"
|
||||
msgstr ""
|
||||
|
||||
msgid "This page displays the temperature sensors data."
|
||||
msgstr ""
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: egemen <95934830+projectaccc@users.noreply.github.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: tr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.9\n"
|
||||
|
||||
msgid "(%d minute window, %d second interval)"
|
||||
msgstr "(%d dakikalık aralık, %d saniyelik sıklık)"
|
||||
|
||||
msgid "Average:"
|
||||
msgstr "Ortalama:"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "Gizle"
|
||||
|
||||
msgid "Hot:"
|
||||
msgstr "Sıcak:"
|
||||
|
||||
msgid "Minimum:"
|
||||
msgstr "En Düşük:"
|
||||
|
||||
msgid "No temperature sensors available"
|
||||
msgstr "Kullanılabilir sıcaklık sensörü yok"
|
||||
|
||||
msgid "Overheat:"
|
||||
msgstr "Aşırı Isınma:"
|
||||
|
||||
msgid "Peak:"
|
||||
msgstr "En Yüksek:"
|
||||
|
||||
msgid "Sensor"
|
||||
msgstr "Sensör"
|
||||
|
||||
msgid "Show hidden sensors"
|
||||
msgstr "Gizlenmiş sensörleri göster"
|
||||
|
||||
msgid "Switch view"
|
||||
msgstr "Görünümü değiştir"
|
||||
|
||||
msgid "Temperature"
|
||||
msgstr "Sıcaklık"
|
||||
|
||||
msgid "This page displays the temperature sensors data."
|
||||
msgstr "Bu sayfada sıcaklık sensörü verileri görüntülenir."
|
||||
@@ -1,52 +0,0 @@
|
||||
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 "Average:"
|
||||
msgstr "平均:"
|
||||
|
||||
msgid "Hide"
|
||||
msgstr "隐藏"
|
||||
|
||||
msgid "Hot:"
|
||||
msgstr "最热:"
|
||||
|
||||
msgid "Minimum:"
|
||||
msgstr "最低:"
|
||||
|
||||
msgid "No temperature sensors available"
|
||||
msgstr "温度传感器不可用"
|
||||
|
||||
msgid "Overheat:"
|
||||
msgstr "过热:"
|
||||
|
||||
msgid "Peak:"
|
||||
msgstr "峰值:"
|
||||
|
||||
msgid "Sensor"
|
||||
msgstr "传感器"
|
||||
|
||||
msgid "Show hidden sensors"
|
||||
msgstr "显示已隐藏的传感器"
|
||||
|
||||
msgid "Switch view"
|
||||
msgstr "切换视图"
|
||||
|
||||
msgid "Temperature"
|
||||
msgstr "温度"
|
||||
|
||||
msgid "This page displays the temperature sensors data."
|
||||
msgstr "此页面显示温度传感器的信息。"
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"admin/status/realtime/temperature": {
|
||||
"title": "Temperature",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "status/temperature"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-temp-status" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"luci-app-temp-status": {
|
||||
"description": "Grant access to temp-status procedures",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.temp-status": [ "getSensors", "getTempData", "getTempStatus" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { chdir, lsdir, readfile, readlink, realpath, stat } from 'fs';
|
||||
|
||||
const sysHwmon = '/sys/class/hwmon';
|
||||
const sysThermal = '/sys/class/thermal';
|
||||
|
||||
function readFile(path) {
|
||||
let r = readfile(path);
|
||||
return r && trim(r);
|
||||
}
|
||||
|
||||
function findTPoints(thermalDirPath) {
|
||||
let tPoints = {};
|
||||
if(stat(thermalDirPath)?.type == 'directory') {
|
||||
for(let tItem in lsdir(thermalDirPath)) {
|
||||
if(match(tItem, /^trip_point_[0-9]+_type$/)) {
|
||||
let pTypePath = sprintf('%s/%s', thermalDirPath, tItem);
|
||||
let m = match(tItem, /[0-9]+/);
|
||||
let pNumber = m && m[0];
|
||||
let pType = readFile(pTypePath);
|
||||
let pTemp = readFile(replace(pTypePath, /_type$/, '_temp'));
|
||||
if(pNumber && pType && pTemp) {
|
||||
tPoints[pNumber] = { type: pType, temp: int(pTemp) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (length(tPoints) > 0) ? tPoints : null;
|
||||
}
|
||||
|
||||
function getHwmonData(tDevPath, tempData) {
|
||||
let hwmon = [];
|
||||
if(stat(sysHwmon)?.type == 'directory') {
|
||||
for(let item in lsdir(sysHwmon)) {
|
||||
if(match(item, /^hwmon[0-9]+$/)) {
|
||||
let hwmonDirPath = sprintf('%s/%s', sysHwmon, item);
|
||||
let deviceDirPath = readlink(sprintf('%s/device', hwmonDirPath));
|
||||
if(deviceDirPath) {
|
||||
chdir(hwmonDirPath);
|
||||
let path = realpath(deviceDirPath);
|
||||
if(path) {
|
||||
tDevPath[path] = true;
|
||||
}
|
||||
}
|
||||
if(stat(hwmonDirPath)?.type == 'directory') {
|
||||
let m = match(item, /[0-9]+/);
|
||||
let dNumber = m && m[0];
|
||||
let title = readFile(hwmonDirPath + '/name');
|
||||
let sources = [];
|
||||
for(let source in lsdir(hwmonDirPath)) {
|
||||
if(match(source, /^temp[0-9]+_input$/)) {
|
||||
let tPoints = {};
|
||||
let m = match(source, /[0-9]+/);
|
||||
let sNumber = m && m[0];
|
||||
let sourceFilePath = sprintf('%s/%s', hwmonDirPath, source);
|
||||
let temp = readFile(sourceFilePath);
|
||||
if(sNumber && temp) {
|
||||
tempData[sourceFilePath] = int(temp);
|
||||
let label = readFile(sprintf(
|
||||
"%s/%s", hwmonDirPath, replace(source, /_input$/, '_label')));
|
||||
let tPointEmer = readFile(sprintf(
|
||||
"%s/%s", hwmonDirPath, replace(source, /_input$/, '_emergency')));
|
||||
if(tPointEmer) {
|
||||
tPoints['0'] = { type: 'emergency', temp: int(tPointEmer) };
|
||||
}
|
||||
let tPointCrit = readFile(sprintf(
|
||||
"%s/%s", hwmonDirPath, replace(source, /_input$/, '_crit')));
|
||||
if(tPointCrit) {
|
||||
tPoints['1'] = { type: 'critical', temp: int(tPointCrit) };
|
||||
}
|
||||
let tPointMax = readFile(sprintf(
|
||||
"%s/%s", hwmonDirPath, replace(source, /_input$/, '_max')));
|
||||
if(tPointMax) {
|
||||
tPoints['2'] = { type: 'max', temp: int(tPointMax) };
|
||||
}
|
||||
if(deviceDirPath && length(tPoints) == 0) {
|
||||
tPoints = (findTPoints(deviceDirPath) || tPoints);
|
||||
}
|
||||
let sDict = {
|
||||
number: int(sNumber),
|
||||
item : source,
|
||||
path : sourceFilePath,
|
||||
temp : int(temp),
|
||||
};
|
||||
if(label) {
|
||||
sDict['label'] = label;
|
||||
};
|
||||
if(length(tPoints) > 0) {
|
||||
sDict['tpoints'] = tPoints;
|
||||
}
|
||||
push(sources, sDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(dNumber && length(sources) > 0) {
|
||||
let d = {
|
||||
number : int(dNumber),
|
||||
item : item,
|
||||
sources: sources,
|
||||
};
|
||||
if(title) {
|
||||
d['title'] = title;
|
||||
}
|
||||
push(hwmon, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hwmon;
|
||||
}
|
||||
|
||||
function getThermalData(tDevPath, tempData) {
|
||||
let thermal = [];
|
||||
if(stat(sysThermal)?.type == 'directory') {
|
||||
chdir(sysThermal);
|
||||
for(let item in lsdir(sysThermal)) {
|
||||
if(match(item, /^thermal_zone[0-9]+$/)) {
|
||||
let thermalDirPath = sprintf('%s/%s', sysThermal, item);
|
||||
let deviceDirPath = readlink(thermalDirPath);
|
||||
if(deviceDirPath && exists(tDevPath, realpath(deviceDirPath))) {
|
||||
continue;
|
||||
}
|
||||
let m = match(item, /[0-9]+/);
|
||||
let number = m && m[0];
|
||||
let tempFilePath = thermalDirPath + '/temp';
|
||||
let temp = readFile(tempFilePath);
|
||||
if(number && temp) {
|
||||
tempData[tempFilePath] = int(temp);
|
||||
let title = readFile(thermalDirPath + '/type');
|
||||
let tPoints = findTPoints(thermalDirPath);
|
||||
let sDict = {
|
||||
number: 0,
|
||||
path : tempFilePath,
|
||||
temp : int(temp),
|
||||
};
|
||||
if(tPoints && length(tPoints) > 0) {
|
||||
sDict['tpoints'] = tPoints;
|
||||
}
|
||||
let tDict = {
|
||||
number : int(number),
|
||||
item : item,
|
||||
sources: [ sDict ],
|
||||
};
|
||||
if(title) {
|
||||
tDict['title'] = title;
|
||||
}
|
||||
push(thermal, tDict);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return thermal;
|
||||
}
|
||||
|
||||
function getSensors() {
|
||||
const tDevPath = {};
|
||||
const tempData = {};
|
||||
let sensors = {};
|
||||
let hwmon = getHwmonData(tDevPath, tempData);
|
||||
if(length(hwmon) > 0) {
|
||||
sensors['0'] = hwmon;
|
||||
}
|
||||
let thermal = getThermalData(tDevPath, tempData);
|
||||
if(length(thermal) > 0) {
|
||||
sensors['1'] = thermal;
|
||||
}
|
||||
return { sensors: sensors, temp: tempData };
|
||||
}
|
||||
|
||||
const methods = {
|
||||
getSensors: {
|
||||
call: getSensors
|
||||
},
|
||||
|
||||
getTempData: {
|
||||
args: { tpaths: [] },
|
||||
call: function(request) {
|
||||
const tpaths = request.args?.tpaths;
|
||||
let tData = {};
|
||||
if(tpaths && length(tpaths) > 0) {
|
||||
for(let i in tpaths) {
|
||||
let t = readFile(i);
|
||||
tData[i] = (t && int(t));
|
||||
}
|
||||
}
|
||||
return { temp: tData };
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* For compatibility with <= v0.6.
|
||||
* Will be removed in the future.
|
||||
*/
|
||||
getTempStatus: {
|
||||
call: function() { return getSensors().sensors }
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.temp-status': methods };
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 115 KiB |
Reference in new issue
Block a user