mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 20:48:17 +08:00
This commit is contained in:
1 parent
4ddea70140
commit
f7d3c64ed4
32 files changed
+954
-1622
No files matched your search
@@ -15,7 +15,6 @@ config DOCKER_CGROUP_OPTIONS
|
||||
select KERNEL_CGROUP_FREEZER
|
||||
select KERNEL_NET_CLS_CGROUP
|
||||
select KERNEL_CGROUP_NET_PRIO
|
||||
select PACKAGE_cgroupfs-mount
|
||||
help
|
||||
Selects kernel options to enable CGroups V1.
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dockerd
|
||||
PKG_VERSION:=29.6.1
|
||||
PKG_RELEASE:=12
|
||||
PKG_RELEASE:=13
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
PKG_CPE_ID:=cpe:/a:mobyproject:moby
|
||||
|
||||
@@ -166,7 +166,7 @@ process_config() {
|
||||
config_get https_proxy proxies https_proxy "${https_proxy}"
|
||||
config_get no_proxy proxies no_proxy "${no_proxy}"
|
||||
config_get storage_driver globals storage_driver ""
|
||||
config_get max_concurrent_downloads globals max_concurrent_downloads ""
|
||||
config_get max_concurrent_downloads globals max_concurrent_downloads "0"
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
json_init
|
||||
@@ -198,7 +198,7 @@ process_config() {
|
||||
fi
|
||||
[ -z "${storage_driver}" ] || json_add_string "storage-driver" "${storage_driver}"
|
||||
|
||||
[ "${max_concurrent_downloads:-0}" -eq "0" ] || json_add_int "max-concurrent-downloads" "${max_concurrent_downloads}"
|
||||
[ "${max_concurrent_downloads}" -eq "0" ] || json_add_int "max-concurrent-downloads" "${max_concurrent_downloads}"
|
||||
json_add_string "log-driver" "json-file"
|
||||
json_add_object "log-opts"
|
||||
json_add_string "max-size" "50m"
|
||||
|
||||
+2
@@ -11,3 +11,5 @@ if [ -n "$DOCKER0_DEVICE" ]; then
|
||||
fi
|
||||
uci -q delete network.docker
|
||||
uci commit
|
||||
|
||||
exit 0
|
||||
@@ -39,7 +39,7 @@ git init --quiet "${GIT_DIR}"
|
||||
|
||||
if git fetch --depth 1 origin "${GIT_REF}"; then
|
||||
git checkout --detach FETCH_HEAD --
|
||||
git rev-parse --short HEAD
|
||||
git rev-parse --short=7 HEAD
|
||||
break
|
||||
fi
|
||||
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=gately
|
||||
PKG_VERSION:=0.6.6
|
||||
PKG_RELEASE:=34
|
||||
PKG_VERSION:=0.6.6.2
|
||||
PKG_RELEASE:=35
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -1,48 +1,21 @@
|
||||
#
|
||||
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-fancontrol
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=7
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Dante <dante@seckv.com>
|
||||
|
||||
LUCI_TITLE:=LuCI support for BPI-R4 PWM Fan Control
|
||||
LUCI_DEPENDS:=+luci-base +ucode +ucode-mod-fs +ucode-mod-uci +ucode-mod-ubus
|
||||
LUCI_TITLE:=LuCI support for the generic PWM fan controller
|
||||
LUCI_DEPENDS:=+luci-base +fancontrol
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/fancontrol
|
||||
endef
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=JiaY-shi <shi05275@163.com>
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||
chmod 755 /etc/init.d/fancontrol /usr/sbin/fancontrold 2>/dev/null
|
||||
/etc/init.d/rpcd restart
|
||||
/etc/init.d/fancontrol enable
|
||||
/etc/init.d/fancontrol restart
|
||||
}
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/prerm
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
/etc/init.d/fancontrol stop
|
||||
/etc/init.d/fancontrol disable
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postrm
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
rm -f /var/log/fancontrol.log*
|
||||
rm -f /var/run/fancontrol.*
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
PKG_NAME:=luci-app-fancontrol
|
||||
PKG_VERSION:=2.2.0
|
||||
PKG_RELEASE:=8
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,171 @@
|
||||
.fancontrol-runtime {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.fancontrol-status {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(127, 127, 127, .28);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.fancontrol-status-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 0;
|
||||
padding: .4rem .7rem;
|
||||
background: rgba(127, 127, 127, .08);
|
||||
border-bottom: 1px solid rgba(127, 127, 127, .22);
|
||||
}
|
||||
|
||||
.fancontrol-status-title {
|
||||
font-size: .88rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fancontrol-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.fancontrol-metric {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
padding: .55rem .7rem;
|
||||
border-right: 1px solid rgba(127, 127, 127, .2);
|
||||
}
|
||||
|
||||
.fancontrol-metric:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.fancontrol-metric-label,
|
||||
.fancontrol-detail-label {
|
||||
display: block;
|
||||
color: var(--text-color-medium, inherit);
|
||||
font-size: .72rem;
|
||||
}
|
||||
|
||||
.fancontrol-metric-reading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: .3rem;
|
||||
min-height: 0;
|
||||
margin-top: .12rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fancontrol-metric-value {
|
||||
font-size: 1.18rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.fancontrol-metric-unit {
|
||||
overflow: hidden;
|
||||
color: var(--text-color-medium, inherit);
|
||||
font-size: .72rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fancontrol-chart-wrap {
|
||||
height: 72px;
|
||||
margin-top: .4rem;
|
||||
}
|
||||
|
||||
.fancontrol-chart {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--text-color-medium, #666);
|
||||
background: rgba(127, 127, 127, .025);
|
||||
border: 1px solid rgba(127, 127, 127, .24);
|
||||
}
|
||||
|
||||
.fancontrol-chart-temperature {
|
||||
--fancontrol-chart-line: #c74b45;
|
||||
--fancontrol-chart-fill: rgba(199, 75, 69, .2);
|
||||
}
|
||||
|
||||
.fancontrol-chart-pwm {
|
||||
--fancontrol-chart-line: #1976d2;
|
||||
--fancontrol-chart-fill: rgba(25, 118, 210, .2);
|
||||
}
|
||||
|
||||
.fancontrol-chart-rpm {
|
||||
--fancontrol-chart-line: #2f8a57;
|
||||
--fancontrol-chart-fill: rgba(47, 138, 87, .2);
|
||||
}
|
||||
|
||||
.fancontrol-details {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: .3rem 1rem;
|
||||
padding: .42rem .7rem;
|
||||
background: rgba(127, 127, 127, .04);
|
||||
border-top: 1px solid rgba(127, 127, 127, .2);
|
||||
}
|
||||
|
||||
.fancontrol-detail {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(7rem, .34fr) minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
gap: .45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fancontrol-detail-value {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.fancontrol-ok {
|
||||
color: var(--success-color-medium, #00884a);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.fancontrol-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.fancontrol-metric:nth-child(2) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.fancontrol-metric:last-child {
|
||||
grid-column: 1 / -1;
|
||||
border-top: 1px solid rgba(127, 127, 127, .2);
|
||||
}
|
||||
|
||||
.fancontrol-details {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.fancontrol-metrics {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.fancontrol-metric,
|
||||
.fancontrol-metric:nth-child(2) {
|
||||
border-right: 0;
|
||||
border-top: 1px solid rgba(127, 127, 127, .2);
|
||||
}
|
||||
|
||||
.fancontrol-metric:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.fancontrol-metric:last-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.fancontrol-detail {
|
||||
grid-template-columns: 1fr;
|
||||
gap: .15rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
'use strict';
|
||||
'require dom';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
'require view';
|
||||
|
||||
const callServiceList = rpc.declare({
|
||||
object: 'service',
|
||||
method: 'list',
|
||||
params: [ 'name' ],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const HISTORY_WINDOW_MS = 2 * 60 * 1000;
|
||||
const TIME_GRID_INTERVAL_MS = 10 * 1000;
|
||||
const TIME_LABEL_INTERVAL_MS = 30 * 1000;
|
||||
const VALUE_GRID_DIVISIONS = 4;
|
||||
|
||||
function serviceRunning(result) {
|
||||
const instances = result?.fancontrol?.instances || {};
|
||||
|
||||
return Object.keys(instances).some(function(name) {
|
||||
return instances[name].running === true;
|
||||
});
|
||||
}
|
||||
|
||||
function parseStatus(text) {
|
||||
const status = {};
|
||||
|
||||
String(text || '').split(/\n/).forEach(function(line) {
|
||||
const separator = line.indexOf('=');
|
||||
|
||||
if (separator > 0)
|
||||
status[line.substring(0, separator)] = line.substring(separator + 1);
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
function validStatusNumber(value) {
|
||||
const number = Number(value);
|
||||
|
||||
return Number.isFinite(number) && number >= 0 ? number : null;
|
||||
}
|
||||
|
||||
function appendHistory(history, running, status) {
|
||||
const now = Date.now();
|
||||
const temperature = validStatusNumber(status.temperature_mc);
|
||||
const pwm = validStatusNumber(status.pwm);
|
||||
const rpm = validStatusNumber(status.rpm);
|
||||
|
||||
history.push({
|
||||
time: now,
|
||||
temperature: running && temperature != null ? temperature / 1000 : null,
|
||||
pwm: running && pwm != null ? pwm * 100 / 255 : null,
|
||||
rpm: running ? rpm : null
|
||||
});
|
||||
|
||||
while (history.length && history[0].time < now - HISTORY_WINDOW_MS)
|
||||
history.shift();
|
||||
}
|
||||
|
||||
function chartScale(history, key, minimumMax, step) {
|
||||
let maximum = minimumMax;
|
||||
|
||||
history.forEach(function(sample) {
|
||||
if (sample[key] != null)
|
||||
maximum = Math.max(maximum, sample[key]);
|
||||
});
|
||||
|
||||
return Math.ceil(maximum / step) * step;
|
||||
}
|
||||
|
||||
function drawChart(canvas, history, key, options) {
|
||||
const style = window.getComputedStyle(canvas);
|
||||
const width = Math.max(canvas.clientWidth, 1);
|
||||
const height = Math.max(canvas.clientHeight, 1);
|
||||
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
const context = canvas.getContext('2d');
|
||||
const padding = { left: 28, right: 5, top: 6, bottom: 14 };
|
||||
const plotWidth = Math.max(width - padding.left - padding.right, 1);
|
||||
const plotHeight = Math.max(height - padding.top - padding.bottom, 1);
|
||||
const plotBottom = padding.top + plotHeight;
|
||||
const now = history.length ? history[history.length - 1].time : Date.now();
|
||||
const start = now - HISTORY_WINDOW_MS;
|
||||
const maximum = chartScale(history, key, options.minimumMax, options.step);
|
||||
const lineColor = style.getPropertyValue('--fancontrol-chart-line').trim() || '#1976d2';
|
||||
const fillColor = style.getPropertyValue('--fancontrol-chart-fill').trim() || 'rgba(25, 118, 210, .2)';
|
||||
const labelColor = style.color || '#666';
|
||||
const gridColor = 'rgba(127, 127, 127, .2)';
|
||||
let segment = [];
|
||||
|
||||
canvas.width = Math.round(width * ratio);
|
||||
canvas.height = Math.round(height * ratio);
|
||||
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
||||
context.clearRect(0, 0, width, height);
|
||||
|
||||
context.strokeStyle = gridColor;
|
||||
context.lineWidth = 1;
|
||||
context.beginPath();
|
||||
for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_GRID_INTERVAL_MS) {
|
||||
const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS;
|
||||
|
||||
context.moveTo(x, padding.top);
|
||||
context.lineTo(x, plotBottom);
|
||||
}
|
||||
for (let division = 0; division <= VALUE_GRID_DIVISIONS; division++) {
|
||||
const y = padding.top + plotHeight * division / VALUE_GRID_DIVISIONS;
|
||||
|
||||
context.moveTo(padding.left, y);
|
||||
context.lineTo(padding.left + plotWidth, y);
|
||||
}
|
||||
context.stroke();
|
||||
|
||||
context.fillStyle = labelColor;
|
||||
context.font = '9px sans-serif';
|
||||
context.textAlign = 'right';
|
||||
context.textBaseline = 'top';
|
||||
context.fillText(options.format(maximum), padding.left - 4, padding.top - 1);
|
||||
context.textBaseline = 'bottom';
|
||||
context.fillText(options.format(0), padding.left - 4, plotBottom + 1);
|
||||
context.textBaseline = 'middle';
|
||||
context.fillText(options.format(maximum / 2), padding.left - 4,
|
||||
padding.top + plotHeight / 2);
|
||||
|
||||
context.textBaseline = 'bottom';
|
||||
for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_LABEL_INTERVAL_MS) {
|
||||
const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS;
|
||||
const remaining = (HISTORY_WINDOW_MS - elapsed) / 1000;
|
||||
|
||||
context.textAlign = elapsed === 0 ? 'left' :
|
||||
elapsed === HISTORY_WINDOW_MS ? 'right' : 'center';
|
||||
context.fillText(remaining ? '-%ds'.format(remaining) : '0', x, height);
|
||||
}
|
||||
|
||||
function point(sample) {
|
||||
return {
|
||||
x: padding.left + (sample.time - start) / HISTORY_WINDOW_MS * plotWidth,
|
||||
y: plotBottom - sample[key] / maximum * plotHeight
|
||||
};
|
||||
}
|
||||
|
||||
function drawSegment() {
|
||||
if (!segment.length)
|
||||
return;
|
||||
|
||||
if (segment.length > 1) {
|
||||
context.beginPath();
|
||||
context.moveTo(segment[0].x, plotBottom);
|
||||
segment.forEach(function(item) { context.lineTo(item.x, item.y); });
|
||||
context.lineTo(segment[segment.length - 1].x, plotBottom);
|
||||
context.closePath();
|
||||
context.fillStyle = fillColor;
|
||||
context.fill();
|
||||
|
||||
context.beginPath();
|
||||
segment.forEach(function(item, index) {
|
||||
if (index)
|
||||
context.lineTo(item.x, item.y);
|
||||
else
|
||||
context.moveTo(item.x, item.y);
|
||||
});
|
||||
context.strokeStyle = lineColor;
|
||||
context.lineWidth = 1.5;
|
||||
context.lineJoin = 'round';
|
||||
context.stroke();
|
||||
} else {
|
||||
context.beginPath();
|
||||
context.arc(segment[0].x, segment[0].y, 2, 0, Math.PI * 2);
|
||||
context.fillStyle = lineColor;
|
||||
context.fill();
|
||||
}
|
||||
|
||||
segment = [];
|
||||
}
|
||||
|
||||
history.forEach(function(sample) {
|
||||
if (sample[key] == null) {
|
||||
drawSegment();
|
||||
return;
|
||||
}
|
||||
|
||||
segment.push(point(sample));
|
||||
});
|
||||
drawSegment();
|
||||
}
|
||||
|
||||
function statusMetric(title, value, unit, chartClass) {
|
||||
const canvas = E('canvas', {
|
||||
'class': 'fancontrol-chart fancontrol-chart-%s'.format(chartClass),
|
||||
'role': 'img',
|
||||
'aria-label': title
|
||||
});
|
||||
const children = [
|
||||
E('span', { 'class': 'fancontrol-metric-label' }, title),
|
||||
E('div', { 'class': 'fancontrol-metric-reading' }, [
|
||||
E('span', { 'class': 'fancontrol-metric-value' }, value),
|
||||
unit ? E('span', { 'class': 'fancontrol-metric-unit' }, unit) : ''
|
||||
]),
|
||||
E('div', { 'class': 'fancontrol-chart-wrap' }, canvas)
|
||||
];
|
||||
|
||||
return {
|
||||
node: E('div', { 'class': 'fancontrol-metric' }, children),
|
||||
canvas: canvas
|
||||
};
|
||||
}
|
||||
|
||||
function statusDetail(title, value) {
|
||||
return E('div', { 'class': 'fancontrol-detail' }, [
|
||||
E('span', { 'class': 'fancontrol-detail-label' }, title),
|
||||
E('span', { 'class': 'fancontrol-detail-value' }, value)
|
||||
]);
|
||||
}
|
||||
|
||||
function renderStatus(node, running, status, history) {
|
||||
const temp = Number(status.temperature_mc);
|
||||
const pwm = Number(status.pwm);
|
||||
const rpm = Number(status.rpm);
|
||||
const tempValid = Number.isFinite(temp) && temp >= 0;
|
||||
const pwmValid = Number.isFinite(pwm) && pwm >= 0;
|
||||
const rpmValid = Number.isFinite(rpm) && rpm >= 0;
|
||||
const pwmPercent = pwmValid ? Math.round(pwm * 100 / 255) : null;
|
||||
const fault = status.error
|
||||
? E('span', { 'class': 'label warning' }, status.error)
|
||||
: E('span', { 'class': 'fancontrol-ok' }, _('None'));
|
||||
const temperatureMetric = statusMetric(_('Temperature'),
|
||||
tempValid ? '%.1f'.format(temp / 1000) : '-', tempValid ? '°C' : '', 'temperature');
|
||||
const pwmMetric = statusMetric(_('PWM output'), pwmValid ? String(pwm) : '-',
|
||||
pwmValid ? '/ 255 · %d%%'.format(pwmPercent) : '', 'pwm');
|
||||
const rpmMetric = statusMetric(_('Fan speed'), rpmValid ? String(rpm) : '-',
|
||||
rpmValid ? 'RPM' : '', 'rpm');
|
||||
|
||||
dom.content(node, E('div', { 'class': 'fancontrol-status' }, [
|
||||
E('div', { 'class': 'fancontrol-status-head' }, [
|
||||
E('span', { 'class': 'fancontrol-status-title' }, _('Runtime Status')),
|
||||
E('span', { 'class': running ? 'label success' : 'label warning' },
|
||||
running ? _('Running') : _('Stopped'))
|
||||
]),
|
||||
E('div', { 'class': 'fancontrol-metrics' }, [
|
||||
temperatureMetric.node,
|
||||
pwmMetric.node,
|
||||
rpmMetric.node
|
||||
]),
|
||||
E('div', { 'class': 'fancontrol-details' }, [
|
||||
statusDetail(_('Temperature input'), status.thermal_file || '-'),
|
||||
statusDetail(_('PWM device'), status.fan_file || '-'),
|
||||
statusDetail(_('Fault'), fault)
|
||||
])
|
||||
]));
|
||||
|
||||
drawChart(temperatureMetric.canvas, history, 'temperature', {
|
||||
minimumMax: 100,
|
||||
step: 10,
|
||||
format: function(value) { return '%d°'.format(value); }
|
||||
});
|
||||
drawChart(pwmMetric.canvas, history, 'pwm', {
|
||||
minimumMax: 100,
|
||||
step: 100,
|
||||
format: function(value) { return '%d%%'.format(value); }
|
||||
});
|
||||
drawChart(rpmMetric.canvas, history, 'rpm', {
|
||||
minimumMax: 1000,
|
||||
step: 500,
|
||||
format: function(value) { return String(value); }
|
||||
});
|
||||
}
|
||||
|
||||
function validateAutoPath(sectionId, value) {
|
||||
if (value === 'auto' || value?.startsWith('/'))
|
||||
return true;
|
||||
|
||||
return _('Enter "auto" or an absolute sysfs path.');
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return uci.load('fancontrol');
|
||||
},
|
||||
|
||||
render() {
|
||||
let m, s, o;
|
||||
let startTemp, fullTemp, startSpeed, maxSpeed, kickSpeed, kickMs;
|
||||
|
||||
m = new form.Map('fancontrol', _('Fan Control'),
|
||||
_('Configure a continuous PWM curve based on device temperature. Automatic hardware detection is suitable for most devices.'));
|
||||
|
||||
s = m.section(form.TypedSection, 'fancontrol', _('Runtime Status'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
s.render = function() {
|
||||
const history = [];
|
||||
const node = E('div', { 'class': 'cbi-section fancontrol-runtime' }, [
|
||||
E('link', {
|
||||
'rel': 'stylesheet',
|
||||
'href': L.resource('view/fancontrol.css')
|
||||
}),
|
||||
E('div', { 'class': 'cbi-section-node' }, _('Collecting data...'))
|
||||
]);
|
||||
const statusNode = node.lastElementChild;
|
||||
|
||||
poll.add(function() {
|
||||
return Promise.all([
|
||||
L.resolveDefault(callServiceList('fancontrol'), {}),
|
||||
L.resolveDefault(fs.read_direct('/var/run/fancontrol.status'), '')
|
||||
]).then(function(result) {
|
||||
const running = serviceRunning(result[0]);
|
||||
const status = parseStatus(result[1]);
|
||||
|
||||
appendHistory(history, running, status);
|
||||
renderStatus(statusNode, running, status, history);
|
||||
});
|
||||
}, 5);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
s = m.section(form.NamedSection, 'settings', 'fancontrol', _('Settings'));
|
||||
s.addremove = false;
|
||||
s.tab('curve', _('Temperature Curve'),
|
||||
_('Controls when the fan starts and how its speed rises with temperature.'));
|
||||
s.tab('hardware', _('Hardware'),
|
||||
_('Automatic detection works on most devices. Select explicit sysfs entries only when necessary.'));
|
||||
s.tab('safety', _('Safety'),
|
||||
_('Controls startup assistance and fail-safe behavior. Full-speed safety defaults are recommended.'));
|
||||
|
||||
o = s.taboption('curve', form.Flag, 'enabled', _('Enable'));
|
||||
o.default = o.disabled;
|
||||
o.rmempty = false;
|
||||
o.description = _('Run the controller and apply this temperature curve.');
|
||||
|
||||
startTemp = s.taboption('curve', form.Value, 'start_temp', _('Start temperature'));
|
||||
startTemp.default = '45';
|
||||
startTemp.datatype = 'range(-40,200)';
|
||||
startTemp.rmempty = false;
|
||||
startTemp.description = _('The fan remains off below this temperature and starts at the minimum running PWM when it is reached. Unit: °C.');
|
||||
|
||||
fullTemp = s.taboption('curve', form.Value, 'full_speed_temp', _('Full-speed temperature'));
|
||||
fullTemp.default = '85';
|
||||
fullTemp.datatype = 'range(-39,250)';
|
||||
fullTemp.rmempty = false;
|
||||
fullTemp.description = _('PWM rises linearly and reaches the configured maximum at this temperature. Unit: °C.');
|
||||
fullTemp.validate = function(sectionId, value) {
|
||||
if (Number(value) <= Number(startTemp.formvalue(sectionId)))
|
||||
return _('Full-speed temperature must be higher than start temperature.');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('curve', form.Value, 'hysteresis', _('Stop hysteresis'));
|
||||
o.default = '3';
|
||||
o.datatype = 'range(0,50)';
|
||||
o.rmempty = false;
|
||||
o.description = _('After starting, the fan stops only after temperature falls this many degrees below the start temperature. This prevents rapid on/off cycling.');
|
||||
|
||||
startSpeed = s.taboption('curve', form.Value, 'start_speed', _('Minimum running PWM'));
|
||||
startSpeed.default = '64';
|
||||
startSpeed.datatype = 'range(1,255)';
|
||||
startSpeed.rmempty = false;
|
||||
startSpeed.description = _('Lowest PWM used while the fan is running. Increase it if the fan stalls or cannot maintain rotation. Range: 1–255.');
|
||||
|
||||
maxSpeed = s.taboption('curve', form.Value, 'max_speed', _('Maximum PWM'));
|
||||
maxSpeed.default = '255';
|
||||
maxSpeed.datatype = 'range(1,255)';
|
||||
maxSpeed.rmempty = false;
|
||||
maxSpeed.description = _('Highest PWM allowed by the curve. A value of 255 means 100% duty cycle.');
|
||||
maxSpeed.validate = function(sectionId, value) {
|
||||
if (Number(value) < Number(startSpeed.formvalue(sectionId)))
|
||||
return _('Maximum PWM must not be lower than minimum running PWM.');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('curve', form.Value, 'interval', _('Polling interval'));
|
||||
o.default = '5';
|
||||
o.datatype = 'range(1,300)';
|
||||
o.rmempty = false;
|
||||
o.description = _('How often the temperature is read and the PWM output is updated. Unit: seconds.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'thermal_file', _('Temperature input'));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
o.validate = validateAutoPath;
|
||||
o.description = _('Use "auto" to select a CPU or SoC thermal sensor, or enter an absolute thermal zone or hwmon tempN_input path.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'thermal_zone', _('Thermal zone type'));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
o.description = _('Preferred thermal zone type when temperature input is automatic, for example cpu_top_thermal. Leave as "auto" unless several sensors are available.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'fan_file', _('PWM output'));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
o.validate = validateAutoPath;
|
||||
o.description = _('Use "auto" to select a writable hwmon pwmN output, or enter its absolute path. cooling_device cur_state is not a PWM output.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'fan_hwmon', _('Hwmon device name'));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
o.description = _('Preferred hwmon name when PWM output is automatic. The pwmfan driver is preferred by default.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'enable_file', _('PWM mode control'));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
o.validate = validateAutoPath;
|
||||
o.description = _('Optional pwmN_enable path used to select manual PWM mode. With "auto", the matching control is used when available.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'cooling_device', _('Kernel cooling device'));
|
||||
o.default = 'auto';
|
||||
o.value('auto');
|
||||
o.value('none');
|
||||
o.rmempty = false;
|
||||
o.validate = function(sectionId, value) {
|
||||
return value === 'none' ? true : validateAutoPath(sectionId, value);
|
||||
};
|
||||
o.description = _('Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere.');
|
||||
|
||||
o = s.taboption('hardware', form.Value, 'temp_div', _('Temperature divisor'));
|
||||
o.default = '1000';
|
||||
o.value('1');
|
||||
o.value('1000');
|
||||
o.datatype = 'range(1,1000000)';
|
||||
o.rmempty = false;
|
||||
o.description = _('Divides the raw sensor value before control calculations. Standard Linux temperature inputs use 1000; sensors reporting whole degrees use 1.');
|
||||
|
||||
kickSpeed = s.taboption('safety', form.Value, 'kick_speed', _('Start kick PWM'));
|
||||
kickSpeed.default = '255';
|
||||
kickSpeed.datatype = 'range(0,255)';
|
||||
kickSpeed.rmempty = false;
|
||||
kickSpeed.description = _('Brief PWM applied when a stopped fan starts. It helps fans that cannot start reliably at low duty. Set to 0 together with a zero duration to disable.');
|
||||
|
||||
kickMs = s.taboption('safety', form.Value, 'kick_ms', _('Start kick duration'));
|
||||
kickMs.default = '500';
|
||||
kickMs.datatype = 'range(0,10000)';
|
||||
kickMs.rmempty = false;
|
||||
kickMs.description = _('How long the startup kick is applied. Unit: milliseconds.');
|
||||
kickMs.validate = function(sectionId, value) {
|
||||
if (Number(value) > 0 && Number(kickSpeed.formvalue(sectionId)) === 0)
|
||||
return _('Start kick PWM must be non-zero when a kick duration is configured.');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.taboption('safety', form.Value, 'fail_safe_speed', _('Sensor failure PWM'));
|
||||
o.default = '255';
|
||||
o.datatype = 'range(0,255)';
|
||||
o.rmempty = false;
|
||||
o.description = _('PWM used when the temperature sensor cannot be read. Keeping 255 is the safest choice.');
|
||||
|
||||
o = s.taboption('safety', form.Value, 'exit_speed', _('Service exit PWM'));
|
||||
o.default = '255';
|
||||
o.datatype = 'range(0,255)';
|
||||
o.rmempty = false;
|
||||
o.description = _('PWM written when the service stops. Keeping the fan at full speed avoids losing cooling after an unexpected exit.');
|
||||
|
||||
o = s.taboption('safety', form.Flag, 'debug', _('Debug logging'));
|
||||
o.default = o.disabled;
|
||||
o.rmempty = false;
|
||||
o.description = _('Write temperature, PWM and RPM values to the system log at every polling interval.');
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
'require ui';
|
||||
|
||||
var callGetLogs = rpc.declare({
|
||||
object: 'luci.fancontrol',
|
||||
method: 'get_logs',
|
||||
params: [ 'lines' ]
|
||||
});
|
||||
|
||||
var callClearLogs = rpc.declare({
|
||||
object: 'luci.fancontrol',
|
||||
method: 'clear_logs'
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
|
||||
render: function() {
|
||||
var area = E('textarea', {
|
||||
id: 'fancontrol_log_content',
|
||||
style: 'width:100%; height:460px; background:#f9f9f9; color:#222; padding:12px; border:1px solid #ccc; border-radius:4px; font-family:monospace; font-size:12px; line-height:1.5; overflow-y:auto; white-space:pre-wrap; word-break:break-all;',
|
||||
readonly: 'readonly'
|
||||
}, _('正在加载日志...'));
|
||||
|
||||
var btnRefresh = E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'style': 'margin-right:10px;',
|
||||
click: ui.createHandlerFn(this, 'updateLogs')
|
||||
}, _('刷新日志'));
|
||||
|
||||
var btnClear = E('button', {
|
||||
'class': 'btn cbi-button cbi-button-reset',
|
||||
click: ui.createHandlerFn(this, 'handleClearLogs')
|
||||
}, _('清空日志'));
|
||||
|
||||
poll.add(L.bind(this.updateLogs, this), 5);
|
||||
|
||||
// 初始加载一次日志
|
||||
this.updateLogs();
|
||||
|
||||
return E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, _('风扇控制') + ' - ' + _('运行日志')),
|
||||
E('div', { 'class': 'cbi-map-descr' }, _('实时展示温控守护进程的调速动作、温度变化与自检日志。')),
|
||||
E('fieldset', { 'class': 'cbi-section' }, [
|
||||
E('legend', {}, _('系统日志')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('div', { 'style': 'margin-bottom:10px;' }, [ btnRefresh, btnClear ]),
|
||||
area
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
updateLogs: function() {
|
||||
return callGetLogs(100).then(function(res) {
|
||||
var area = document.getElementById('fancontrol_log_content');
|
||||
if (area) {
|
||||
if (res && res.logs && res.logs.length > 0) {
|
||||
area.value = res.logs.join('\n');
|
||||
area.scrollTop = area.scrollHeight;
|
||||
} else {
|
||||
area.value = _('暂无日志记录。');
|
||||
}
|
||||
}
|
||||
}).catch(function(err) {
|
||||
var area = document.getElementById('fancontrol_log_content');
|
||||
if (area) area.value = _('读取日志失败: %s').format(err.message);
|
||||
});
|
||||
},
|
||||
|
||||
handleClearLogs: function() {
|
||||
return callClearLogs().then(L.bind(function(res) {
|
||||
if (res && res.success) {
|
||||
ui.addNotification(null, E('p', _('日志已成功清空。')), 'info');
|
||||
this.updateLogs();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('清空日志失败。')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,329 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
'require ui';
|
||||
'require dom';
|
||||
|
||||
var callGetStatus = rpc.declare({
|
||||
object: 'luci.fancontrol',
|
||||
method: 'get_status'
|
||||
});
|
||||
|
||||
var callSetLevel = rpc.declare({
|
||||
object: 'luci.fancontrol',
|
||||
method: 'set_level',
|
||||
params: [ 'level' ]
|
||||
});
|
||||
|
||||
var callServiceAction = rpc.declare({
|
||||
object: 'luci.fancontrol',
|
||||
method: 'service',
|
||||
params: [ 'action' ]
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null,
|
||||
|
||||
load: function() {
|
||||
return callGetStatus();
|
||||
},
|
||||
|
||||
render: function(status) {
|
||||
var container = E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, _('风扇控制') + ' - ' + _('状态概览')),
|
||||
E('div', { 'class': 'cbi-map-descr' }, _('实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。'))
|
||||
]);
|
||||
|
||||
// 运行状态卡片
|
||||
var statusSection = E('fieldset', { 'class': 'cbi-section' }, [
|
||||
E('legend', {}, _('运行状态')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left', 'width': '33%' }, _('服务状态')),
|
||||
E('td', { 'class': 'td left' }, [
|
||||
E('span', { id: 'fan-service-state', style: 'font-weight:bold; margin-right:15px;' }, '-'),
|
||||
E('button', {
|
||||
id: 'fan-restart-btn',
|
||||
'class': 'btn cbi-button cbi-button-reload',
|
||||
click: ui.createHandlerFn(this, 'handleServiceAction', 'restart')
|
||||
}, _('重启服务'))
|
||||
])
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, _('运行模式')),
|
||||
E('td', { 'class': 'td left', 'id': 'fan-service-mode', 'style': 'font-weight:bold;' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, _('处理器温度')),
|
||||
E('td', { 'class': 'td left', 'id': 'fan-cpu-temp', 'style': 'font-size:16px; font-weight:bold;' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, _('当前风扇档位')),
|
||||
E('td', { 'class': 'td left', 'id': 'fan-cur-level', 'style': 'font-weight:bold;' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, _('脉宽占空比')),
|
||||
E('td', { 'class': 'td left', 'id': 'fan-pwm-duty' }, '-')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
container.appendChild(statusSection);
|
||||
|
||||
// 当前策略配置卡片
|
||||
var policySection = E('fieldset', { 'class': 'cbi-section' }, [
|
||||
E('legend', {}, _('当前策略配置')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-interval' }, [
|
||||
E('td', { 'class': 'td left', 'width': '33%' }, _('检测轮询周期')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-interval' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-spin' }, [
|
||||
E('td', { 'class': 'td left' }, _('启动除尘自检')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-start-spin' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-low' }, [
|
||||
E('td', { 'class': 'td left' }, _('低速触发阈值')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-temp-low' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-med' }, [
|
||||
E('td', { 'class': 'td left' }, _('中速触发阈值')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-temp-med' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-high' }, [
|
||||
E('td', { 'class': 'td left' }, _('全速触发阈值')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-temp-high' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-hyst' }, [
|
||||
E('td', { 'class': 'td left' }, _('换挡回差温差')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-hysteresis' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-manual' }, [
|
||||
E('td', { 'class': 'td left' }, _('预设运转档位')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-manual-level' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', id: 'policy-row-kernel' }, [
|
||||
E('td', { 'class': 'td left' }, _('控制驱动策略')),
|
||||
E('td', { 'class': 'td left', 'id': 'policy-kernel-desc' }, _('系统内核设备树原生驱动接管'))
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
container.appendChild(policySection);
|
||||
|
||||
// 快速测试面板
|
||||
var testSection = E('fieldset', { 'class': 'cbi-section' }, [
|
||||
E('legend', {}, _('硬件快速测试')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('p', { 'style': 'margin-bottom:15px; color:#666;' },
|
||||
_('点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。')),
|
||||
E('div', { 'style': 'display:flex; gap:10px; flex-wrap:wrap;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-reset',
|
||||
click: ui.createHandlerFn(this, 'handleSetLevel', 0)
|
||||
}, _('停止风扇 (0 级)')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
click: ui.createHandlerFn(this, 'handleSetLevel', 1)
|
||||
}, _('低速测试 (1 级)')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
click: ui.createHandlerFn(this, 'handleSetLevel', 2)
|
||||
}, _('中速测试 (2 级)')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply',
|
||||
click: ui.createHandlerFn(this, 'handleSetLevel', 3)
|
||||
}, _('全速测试 (3 级)'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
container.appendChild(testSection);
|
||||
|
||||
// 初始刷新
|
||||
this.updateStatusView(status);
|
||||
|
||||
// 启动定时轮询 (每 3 秒)
|
||||
poll.add(L.bind(function() {
|
||||
return callGetStatus().then(L.bind(function(res) {
|
||||
this.updateStatusView(res);
|
||||
}, this));
|
||||
}, this), 3);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
updateStatusView: function(status) {
|
||||
if (!status) return;
|
||||
|
||||
var stateEl = document.getElementById('fan-service-state');
|
||||
if (stateEl) {
|
||||
if (status.state === 'managed') {
|
||||
stateEl.textContent = _('运行中 [受控]') + (status.pid ? ' [PID: ' + status.pid + ']' : '');
|
||||
stateEl.style.color = '#2e7d32';
|
||||
} else if (status.state === 'unmanaged') {
|
||||
stateEl.textContent = _('运行中 [非受控]') + (status.pid ? ' [PID: ' + status.pid + ']' : '');
|
||||
stateEl.style.color = '#ef6c00';
|
||||
} else {
|
||||
stateEl.textContent = _('已停止');
|
||||
stateEl.style.color = '#c62828';
|
||||
}
|
||||
}
|
||||
|
||||
var modeEl = document.getElementById('fan-service-mode');
|
||||
if (modeEl) {
|
||||
if (status.mode === 'auto') {
|
||||
modeEl.textContent = _('阶梯温控模式');
|
||||
modeEl.style.color = '#1565c0';
|
||||
} else if (status.mode === 'manual') {
|
||||
modeEl.textContent = _('手动定速模式');
|
||||
modeEl.style.color = '#6a1b9a';
|
||||
} else if (status.mode === 'kernel') {
|
||||
modeEl.textContent = _('内核接管模式');
|
||||
modeEl.style.color = '#455a64';
|
||||
} else {
|
||||
modeEl.textContent = status.mode || '-';
|
||||
modeEl.style.color = '#333';
|
||||
}
|
||||
}
|
||||
|
||||
var tempEl = document.getElementById('fan-cpu-temp');
|
||||
if (tempEl) {
|
||||
var tempVal = parseFloat(status.temp) || 0;
|
||||
tempEl.textContent = tempVal.toFixed(1) + ' °C';
|
||||
if (tempVal >= 75) {
|
||||
tempEl.style.color = '#c62828';
|
||||
} else if (tempVal >= 60) {
|
||||
tempEl.style.color = '#ef6c00';
|
||||
} else {
|
||||
tempEl.style.color = '#2e7d32';
|
||||
}
|
||||
}
|
||||
|
||||
var levelEl = document.getElementById('fan-cur-level');
|
||||
if (levelEl) {
|
||||
var lvl = parseInt(status.level) || 0;
|
||||
var lvlName = '';
|
||||
switch (lvl) {
|
||||
case 0: lvlName = _('0 级 [停转]'); break;
|
||||
case 1: lvlName = _('1 级 [低速 31%]'); break;
|
||||
case 2: lvlName = _('2 级 [中速 50%]'); break;
|
||||
case 3: lvlName = _('3 级 [全速 100%]'); break;
|
||||
default: lvlName = lvl + ' ' + _('级'); break;
|
||||
}
|
||||
levelEl.textContent = lvlName;
|
||||
}
|
||||
|
||||
var pwmEl = document.getElementById('fan-pwm-duty');
|
||||
if (pwmEl) {
|
||||
var pwmVal = parseInt(status.pwm) || 0;
|
||||
var pct = Math.round((pwmVal / 255) * 100);
|
||||
pwmEl.textContent = pwmVal + ' / 255 (' + pct + '%)';
|
||||
}
|
||||
|
||||
// 策略配置数据更新与模式显隐
|
||||
var curMode = status.mode || 'auto';
|
||||
var isAuto = (curMode === 'auto');
|
||||
var isManual = (curMode === 'manual');
|
||||
var isKernel = (curMode === 'kernel');
|
||||
|
||||
var setRowDisplay = function(id, visible) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.style.display = visible ? '' : 'none';
|
||||
};
|
||||
|
||||
setRowDisplay('policy-row-interval', isAuto);
|
||||
setRowDisplay('policy-row-spin', isAuto || isManual);
|
||||
setRowDisplay('policy-row-low', isAuto);
|
||||
setRowDisplay('policy-row-med', isAuto);
|
||||
setRowDisplay('policy-row-high', isAuto);
|
||||
setRowDisplay('policy-row-hyst', isAuto);
|
||||
setRowDisplay('policy-row-manual', isManual);
|
||||
setRowDisplay('policy-row-kernel', isKernel);
|
||||
|
||||
var intervalValEl = document.getElementById('policy-interval');
|
||||
if (intervalValEl && status.interval) {
|
||||
intervalValEl.textContent = status.interval + ' ' + _('秒');
|
||||
}
|
||||
|
||||
var spinValEl = document.getElementById('policy-start-spin');
|
||||
if (spinValEl) {
|
||||
spinValEl.textContent = (status.start_spin == 1) ? _('已开启 (开机全速 2 秒)') : _('未开启');
|
||||
}
|
||||
|
||||
if (isAuto) {
|
||||
var low = parseInt(status.temp_low) || 45;
|
||||
var med = parseInt(status.temp_med) || 60;
|
||||
var high = parseInt(status.temp_high) || 75;
|
||||
var hyst = parseInt(status.hysteresis) || 3;
|
||||
|
||||
var lowEl = document.getElementById('policy-temp-low');
|
||||
if (lowEl) lowEl.textContent = low + ' °C (' + _('低于 %d °C 停转').format(low - hyst) + ')';
|
||||
|
||||
var medEl = document.getElementById('policy-temp-med');
|
||||
if (medEl) medEl.textContent = med + ' °C (' + _('低于 %d °C 降回低速').format(med - hyst) + ')';
|
||||
|
||||
var highEl = document.getElementById('policy-temp-high');
|
||||
if (highEl) highEl.textContent = high + ' °C (' + _('低于 %d °C 降回中速').format(high - hyst) + ')';
|
||||
|
||||
var hystEl = document.getElementById('policy-hysteresis');
|
||||
if (hystEl) hystEl.textContent = hyst + ' °C (' + _('防临界点振荡') + ')';
|
||||
} else if (isManual) {
|
||||
var mLevelEl = document.getElementById('policy-manual-level');
|
||||
if (mLevelEl) {
|
||||
var ml = parseInt(status.manual_level) || 0;
|
||||
var mlName = '';
|
||||
switch (ml) {
|
||||
case 0: mlName = _('0 级 [停转 0%]'); break;
|
||||
case 1: mlName = _('1 级 [低速 31%]'); break;
|
||||
case 2: mlName = _('2 级 [中速 50%]'); break;
|
||||
case 3: mlName = _('3 级 [全速 100%]'); break;
|
||||
default: mlName = ml + ' ' + _('级'); break;
|
||||
}
|
||||
mLevelEl.textContent = mlName;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleSetLevel: function(lvl) {
|
||||
ui.showModal(null, [
|
||||
E('p', { 'class': 'spinning' }, _('正在下发测试档位: %d 级 ...').format(lvl))
|
||||
]);
|
||||
|
||||
return callSetLevel(lvl).then(L.bind(function(res) {
|
||||
ui.hideModal();
|
||||
if (res && res.success) {
|
||||
ui.addNotification(null, E('p', _('风扇测试档位下发成功: %d 级').format(lvl)), 'info');
|
||||
return callGetStatus().then(L.bind(this.updateStatusView, this));
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('风扇档位下发失败,请检查硬件节点。')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleServiceAction: function(action) {
|
||||
ui.showModal(null, [
|
||||
E('p', { 'class': 'spinning' }, _('正在执行服务操作: %s ...').format(action))
|
||||
]);
|
||||
|
||||
return callServiceAction(action).then(L.bind(function(res) {
|
||||
ui.hideModal();
|
||||
if (res && res.success) {
|
||||
ui.addNotification(null, E('p', _('服务操作成功执行。')), 'info');
|
||||
return callGetStatus().then(L.bind(this.updateStatusView, this));
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('服务操作执行失败。')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('RPC 请求失败: %s').format(err.message)), 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('fancontrol', _('风扇控制') + ' - ' + _('温控配置'),
|
||||
_('配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。'));
|
||||
|
||||
s = m.section(form.NamedSection, 'global', 'fancontrol', _('参数设置'));
|
||||
s.anonymous = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('启用服务'),
|
||||
_('开启风扇温控自动守护服务。'));
|
||||
o.rmempty = false;
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.ListValue, 'mode', _('工作模式'),
|
||||
_('选择风扇的控制策略模式。'));
|
||||
o.value('auto', _('阶梯温控模式'));
|
||||
o.value('manual', _('手动定速模式'));
|
||||
o.value('kernel', _('内核接管模式'));
|
||||
o.default = 'auto';
|
||||
|
||||
o = s.option(form.Value, 'interval', _('检测周期 (秒)'),
|
||||
_('温度轮询检测的时间间隔,推荐 3 到 10 秒。'));
|
||||
o.datatype = 'range(1, 60)';
|
||||
o.default = '5';
|
||||
o.depends('mode', 'auto');
|
||||
|
||||
o = s.option(form.Flag, 'start_spin', _('启动自检清洁'),
|
||||
_('服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。'));
|
||||
o.default = '1';
|
||||
o.depends('mode', 'auto');
|
||||
o.depends('mode', 'manual');
|
||||
|
||||
// 手动模式档位
|
||||
o = s.option(form.ListValue, 'manual_level', _('手动常开档位'),
|
||||
_('指定风扇持续运转的固定档位。'));
|
||||
o.value('0', _('0 级 [停转 0%]'));
|
||||
o.value('1', _('1 级 [低速 31%]'));
|
||||
o.value('2', _('2 级 [中速 50%]'));
|
||||
o.value('3', _('3 级 [全速 100%]'));
|
||||
o.default = '2';
|
||||
o.depends('mode', 'manual');
|
||||
|
||||
// 阶梯模式各档温度
|
||||
o = s.option(form.Value, 'temp_low', _('1 级低速触发温度 (°C)'),
|
||||
_('温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。'));
|
||||
o.datatype = 'range(30, 90)';
|
||||
o.default = '45';
|
||||
o.depends('mode', 'auto');
|
||||
|
||||
o = s.option(form.Value, 'temp_med', _('2 级中速触发温度 (°C)'),
|
||||
_('温度达到此数值时提升至 2 级中速 (约 50% 占空比)。'));
|
||||
o.datatype = 'range(35, 100)';
|
||||
o.default = '60';
|
||||
o.depends('mode', 'auto');
|
||||
|
||||
o = s.option(form.Value, 'temp_high', _('3 级全速触发温度 (°C)'),
|
||||
_('温度达到此数值时提升至 3 级全速 (100% 占空比)。'));
|
||||
o.datatype = 'range(40, 110)';
|
||||
o.default = '75';
|
||||
o.depends('mode', 'auto');
|
||||
|
||||
o = s.option(form.Value, 'hysteresis', _('换挡回差温度 (°C)'),
|
||||
_('降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。'));
|
||||
o.datatype = 'range(1, 10)';
|
||||
o.default = '3';
|
||||
o.depends('mode', 'auto');
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -1,261 +0,0 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
||||
msgid "风扇控制"
|
||||
msgstr ""
|
||||
|
||||
msgid "状态概览"
|
||||
msgstr ""
|
||||
|
||||
msgid "温控配置"
|
||||
msgstr ""
|
||||
|
||||
msgid "运行日志"
|
||||
msgstr ""
|
||||
|
||||
msgid "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。"
|
||||
msgstr ""
|
||||
|
||||
msgid "运行状态"
|
||||
msgstr ""
|
||||
|
||||
msgid "服务状态"
|
||||
msgstr ""
|
||||
|
||||
msgid "运行中 [受控]"
|
||||
msgstr ""
|
||||
|
||||
msgid "运行中 [非受控]"
|
||||
msgstr ""
|
||||
|
||||
msgid "已停止"
|
||||
msgstr ""
|
||||
|
||||
msgid "重启服务"
|
||||
msgstr ""
|
||||
|
||||
msgid "运行模式"
|
||||
msgstr ""
|
||||
|
||||
msgid "阶梯温控模式"
|
||||
msgstr ""
|
||||
|
||||
msgid "手动定速模式"
|
||||
msgstr ""
|
||||
|
||||
msgid "内核接管模式"
|
||||
msgstr ""
|
||||
|
||||
msgid "处理器温度"
|
||||
msgstr ""
|
||||
|
||||
msgid "当前风扇档位"
|
||||
msgstr ""
|
||||
|
||||
msgid "脉宽占空比"
|
||||
msgstr ""
|
||||
|
||||
msgid "0 级 [停转]"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 级 [低速 31%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "2 级 [中速 50%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "3 级 [全速 100%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "级"
|
||||
msgstr ""
|
||||
|
||||
msgid "硬件快速测试"
|
||||
msgstr ""
|
||||
|
||||
msgid "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。"
|
||||
msgstr ""
|
||||
|
||||
msgid "停止风扇 (0 级)"
|
||||
msgstr ""
|
||||
|
||||
msgid "低速测试 (1 级)"
|
||||
msgstr ""
|
||||
|
||||
msgid "中速测试 (2 级)"
|
||||
msgstr ""
|
||||
|
||||
msgid "全速测试 (3 级)"
|
||||
msgstr ""
|
||||
|
||||
msgid "正在下发测试档位: %d 级 ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "风扇测试档位下发成功: %d 级"
|
||||
msgstr ""
|
||||
|
||||
msgid "风扇档位下发失败,请检查硬件节点。"
|
||||
msgstr ""
|
||||
|
||||
msgid "正在执行服务操作: %s ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "服务操作成功执行。"
|
||||
msgstr ""
|
||||
|
||||
msgid "服务操作执行失败。"
|
||||
msgstr ""
|
||||
|
||||
msgid "RPC 请求失败: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。"
|
||||
msgstr ""
|
||||
|
||||
msgid "参数设置"
|
||||
msgstr ""
|
||||
|
||||
msgid "启用服务"
|
||||
msgstr ""
|
||||
|
||||
msgid "开启风扇温控自动守护服务。"
|
||||
msgstr ""
|
||||
|
||||
msgid "工作模式"
|
||||
msgstr ""
|
||||
|
||||
msgid "选择风扇的控制策略模式。"
|
||||
msgstr ""
|
||||
|
||||
msgid "检测周期 (秒)"
|
||||
msgstr ""
|
||||
|
||||
msgid "温度轮询检测的时间间隔,推荐 3 到 10 秒。"
|
||||
msgstr ""
|
||||
|
||||
msgid "启动自检清洁"
|
||||
msgstr ""
|
||||
|
||||
msgid "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。"
|
||||
msgstr ""
|
||||
|
||||
msgid "手动常开档位"
|
||||
msgstr ""
|
||||
|
||||
msgid "指定风扇持续运转的固定档位。"
|
||||
msgstr ""
|
||||
|
||||
msgid "0 级 [停转 0%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 级 [低速 31%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "2 级 [中速 50%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "3 级 [全速 100%]"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 级低速触发温度 (°C)"
|
||||
msgstr ""
|
||||
|
||||
msgid "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。"
|
||||
msgstr ""
|
||||
|
||||
msgid "2 级中速触发温度 (°C)"
|
||||
msgstr ""
|
||||
|
||||
msgid "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。"
|
||||
msgstr ""
|
||||
|
||||
msgid "3 级全速触发温度 (°C)"
|
||||
msgstr ""
|
||||
|
||||
msgid "温度达到此数值时提升至 3 级全速 (100% 占空比)。"
|
||||
msgstr ""
|
||||
|
||||
msgid "换挡回差温度 (°C)"
|
||||
msgstr ""
|
||||
|
||||
msgid "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。"
|
||||
msgstr ""
|
||||
|
||||
msgid "正在加载日志..."
|
||||
msgstr ""
|
||||
|
||||
msgid "刷新日志"
|
||||
msgstr ""
|
||||
|
||||
msgid "清空日志"
|
||||
msgstr ""
|
||||
|
||||
msgid "实时展示温控守护进程的调速动作、温度变化与自检日志。"
|
||||
msgstr ""
|
||||
|
||||
msgid "系统日志"
|
||||
msgstr ""
|
||||
|
||||
msgid "暂无日志记录。"
|
||||
msgstr ""
|
||||
|
||||
msgid "读取日志失败: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "日志已成功清空。"
|
||||
msgstr ""
|
||||
|
||||
msgid "清空日志失败。"
|
||||
msgstr ""
|
||||
|
||||
msgid "当前策略配置"
|
||||
msgstr ""
|
||||
|
||||
msgid "检测轮询周期"
|
||||
msgstr ""
|
||||
|
||||
msgid "启动除尘自检"
|
||||
msgstr ""
|
||||
|
||||
msgid "低速触发阈值"
|
||||
msgstr ""
|
||||
|
||||
msgid "中速触发阈值"
|
||||
msgstr ""
|
||||
|
||||
msgid "全速触发阈值"
|
||||
msgstr ""
|
||||
|
||||
msgid "换挡回差温差"
|
||||
msgstr ""
|
||||
|
||||
msgid "预设运转档位"
|
||||
msgstr ""
|
||||
|
||||
msgid "控制驱动策略"
|
||||
msgstr ""
|
||||
|
||||
msgid "系统内核设备树原生驱动接管"
|
||||
msgstr ""
|
||||
|
||||
msgid "秒"
|
||||
msgstr ""
|
||||
|
||||
msgid "已开启 (开机全速 2 秒)"
|
||||
msgstr ""
|
||||
|
||||
msgid "未开启"
|
||||
msgstr ""
|
||||
|
||||
msgid "低于 %d °C 停转"
|
||||
msgstr ""
|
||||
|
||||
msgid "低于 %d °C 降回低速"
|
||||
msgstr ""
|
||||
|
||||
msgid "低于 %d °C 降回中速"
|
||||
msgstr ""
|
||||
|
||||
msgid "防临界点振荡"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,261 +1,293 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8\n"
|
||||
msgstr ""
|
||||
"Project-Id-Version: luci-app-fancontrol 2.2.0\n"
|
||||
"PO-Revision-Date: 2026-07-30 00:00+0800\n"
|
||||
"Last-Translator: JiaY-shi <shi05275@163.com>\n"
|
||||
"Language-Team: Chinese (Simplified Han script)\n"
|
||||
"Language: zh_Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
msgid "风扇控制"
|
||||
msgid "Kernel cooling device"
|
||||
msgstr "内核散热设备"
|
||||
|
||||
msgid "Use auto for a single pwm-fan device, an explicit cooling_device path for multiple fans, or none when kernel fan control is already handled elsewhere."
|
||||
msgstr "单个 pwm-fan 设备使用 auto;多个风扇需指定对应的 cooling_device 路径;已通过其他方式处理内核风扇控制时使用 none。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:173
|
||||
msgid ""
|
||||
"After starting, the fan stops only after temperature falls this many degrees "
|
||||
"below the start temperature. This prevents rapid on/off cycling."
|
||||
msgstr "风扇启动后,温度需要降到比启动温度低这么多度时才会停转,避免风扇频繁启停。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:143
|
||||
msgid ""
|
||||
"Automatic detection works on most devices. Select explicit sysfs entries "
|
||||
"only when necessary."
|
||||
msgstr "自动检测适用于大多数设备,仅在必要时手动指定 sysfs 项。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:238
|
||||
msgid ""
|
||||
"Brief PWM applied when a stopped fan starts. It helps fans that cannot start "
|
||||
"reliably at low duty. Set to 0 together with a zero duration to disable."
|
||||
msgstr "风扇从停止状态启动时短暂使用的 PWM,可帮助低占空比下无法可靠启动的风扇。将此项和助推时长都设为 0 可禁用。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:122
|
||||
msgid "Collecting data..."
|
||||
msgstr "正在采集数据..."
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:111
|
||||
msgid ""
|
||||
"Configure a continuous PWM curve based on device temperature. Automatic "
|
||||
"hardware detection is suitable for most devices."
|
||||
msgstr "根据设备温度配置连续 PWM 调速曲线。自动硬件检测适用于大多数设备。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:145
|
||||
msgid ""
|
||||
"Controls startup assistance and fail-safe behavior. Full-speed safety "
|
||||
"defaults are recommended."
|
||||
msgstr "控制启动助推和故障保护行为,建议保留默认的全速安全设置。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:141
|
||||
msgid "Controls when the fan starts and how its speed rises with temperature."
|
||||
msgstr "控制风扇何时启动,以及转速如何随温度升高。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:263
|
||||
msgid "Debug logging"
|
||||
msgstr "调试日志"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:232
|
||||
msgid ""
|
||||
"Divides the raw sensor value before control calculations. Standard Linux "
|
||||
"temperature inputs use 1000; sensors reporting whole degrees use 1."
|
||||
msgstr "进行控制计算前用于换算传感器原始值。标准 Linux 温度输入使用 1000,直接报告摄氏度的传感器使用 1。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:147
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:98
|
||||
msgid "Enter \"auto\" or an absolute sysfs path."
|
||||
msgstr "请输入 \"auto\" 或 sysfs 绝对路径。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:110
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json:3
|
||||
msgid "Fan Control"
|
||||
msgstr "风扇控制"
|
||||
|
||||
msgid "状态概览"
|
||||
msgstr "状态概览"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:84
|
||||
msgid "Fan speed"
|
||||
msgstr "风扇转速"
|
||||
|
||||
msgid "温控配置"
|
||||
msgstr "温控配置"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:89
|
||||
msgid "Fault"
|
||||
msgstr "故障"
|
||||
|
||||
msgid "运行日志"
|
||||
msgstr "运行日志"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:158
|
||||
msgid "Full-speed temperature"
|
||||
msgstr "全速温度"
|
||||
|
||||
msgid "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。"
|
||||
msgstr "实时监控香蕉派 BPI-R4 处理器温度与 PWM 风扇运行档位,提供硬件即时测试功能。"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:165
|
||||
msgid "Full-speed temperature must be higher than start temperature."
|
||||
msgstr "全速温度必须高于启动温度。"
|
||||
|
||||
msgid "运行状态"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json:3
|
||||
msgid "Grant access to Fan Control"
|
||||
msgstr "授予风扇控制访问权限"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:142
|
||||
msgid "Hardware"
|
||||
msgstr "硬件"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:185
|
||||
msgid "Highest PWM allowed by the curve. A value of 255 means 100% duty cycle."
|
||||
msgstr "温控曲线允许的最高 PWM,255 表示 100% 占空比。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:244
|
||||
msgid "How long the startup kick is applied. Unit: milliseconds."
|
||||
msgstr "启动助推持续的时间,单位为毫秒。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:196
|
||||
msgid ""
|
||||
"How often the temperature is read and the PWM output is updated. Unit: "
|
||||
"seconds."
|
||||
msgstr "读取温度并更新 PWM 输出的时间间隔,单位为秒。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:215
|
||||
msgid "Hwmon device name"
|
||||
msgstr "Hwmon 设备名称"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:179
|
||||
msgid ""
|
||||
"Lowest PWM used while the fan is running. Increase it if the fan stalls or "
|
||||
"cannot maintain rotation. Range: 1–255."
|
||||
msgstr "风扇运行时使用的最低 PWM。如果风扇停转或无法持续旋转,请提高此值。范围:1–255。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:181
|
||||
msgid "Maximum PWM"
|
||||
msgstr "最大 PWM"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:188
|
||||
msgid "Maximum PWM must not be lower than minimum running PWM."
|
||||
msgstr "最大 PWM 不得低于最低运行 PWM。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:175
|
||||
msgid "Minimum running PWM"
|
||||
msgstr "最低运行 PWM"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:71
|
||||
msgid "None"
|
||||
msgstr "无"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:224
|
||||
msgid ""
|
||||
"Optional pwmN_enable path used to select manual PWM mode. With \"auto\", the "
|
||||
"matching control is used when available."
|
||||
msgstr "用于选择手动 PWM 模式的可选 pwmN_enable 路径。设为 \"auto\" 时会自动使用匹配的控制项。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:88
|
||||
msgid "PWM device"
|
||||
msgstr "PWM 设备"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:220
|
||||
msgid "PWM mode control"
|
||||
msgstr "PWM 模式控制"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:82
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:209
|
||||
msgid "PWM output"
|
||||
msgstr "PWM 输出"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:162
|
||||
msgid ""
|
||||
"PWM rises linearly and reaches the configured maximum at this temperature. "
|
||||
"Unit: °C."
|
||||
msgstr "PWM 随温度线性升高,并在此温度达到设定的最大值,单位为 °C。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:255
|
||||
msgid ""
|
||||
"PWM used when the temperature sensor cannot be read. Keeping 255 is the "
|
||||
"safest choice."
|
||||
msgstr "温度传感器读取失败时使用的 PWM。保持 255 是最安全的选择。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:261
|
||||
msgid ""
|
||||
"PWM written when the service stops. Keeping the fan at full speed avoids "
|
||||
"losing cooling after an unexpected exit."
|
||||
msgstr "服务停止时写入的 PWM。保持风扇全速可避免服务意外退出后失去散热。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:192
|
||||
msgid "Polling interval"
|
||||
msgstr "轮询间隔"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:218
|
||||
msgid ""
|
||||
"Preferred hwmon name when PWM output is automatic. The pwmfan driver is "
|
||||
"preferred by default."
|
||||
msgstr "自动选择 PWM 输出时优先使用的 hwmon 名称,默认优先选择 pwmfan 驱动。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:207
|
||||
msgid ""
|
||||
"Preferred thermal zone type when temperature input is automatic, for example "
|
||||
"cpu_top_thermal. Leave as \"auto\" unless several sensors are available."
|
||||
msgstr "自动选择温度输入时优先使用的 thermal zone 类型,例如 cpu_top_thermal。除非存在多个传感器,否则保持 \"auto\"。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:150
|
||||
msgid "Run the controller and apply this temperature curve."
|
||||
msgstr "运行风扇控制器并应用此温控曲线。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:77
|
||||
msgid "Running"
|
||||
msgstr "运行中"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:75
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:113
|
||||
msgid "Runtime Status"
|
||||
msgstr "运行状态"
|
||||
|
||||
msgid "服务状态"
|
||||
msgstr "服务状态"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:144
|
||||
msgid "Safety"
|
||||
msgstr "安全"
|
||||
|
||||
msgid "运行中 [受控]"
|
||||
msgstr "运行中 [受控]"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:251
|
||||
msgid "Sensor failure PWM"
|
||||
msgstr "传感器故障 PWM"
|
||||
|
||||
msgid "运行中 [非受控]"
|
||||
msgstr "运行中 [非受控]"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:257
|
||||
msgid "Service exit PWM"
|
||||
msgstr "服务退出 PWM"
|
||||
|
||||
msgid "已停止"
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:138
|
||||
msgid "Settings"
|
||||
msgstr "设置"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:234
|
||||
msgid "Start kick PWM"
|
||||
msgstr "启动助推 PWM"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:247
|
||||
msgid "Start kick PWM must be non-zero when a kick duration is configured."
|
||||
msgstr "配置启动助推时长时,启动助推 PWM 不能为零。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:240
|
||||
msgid "Start kick duration"
|
||||
msgstr "启动助推时长"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:152
|
||||
msgid "Start temperature"
|
||||
msgstr "启动温度"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:169
|
||||
msgid "Stop hysteresis"
|
||||
msgstr "停转回差"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:77
|
||||
msgid "Stopped"
|
||||
msgstr "已停止"
|
||||
|
||||
msgid "重启服务"
|
||||
msgstr "重启服务"
|
||||
|
||||
msgid "运行模式"
|
||||
msgstr "运行模式"
|
||||
|
||||
msgid "阶梯温控模式"
|
||||
msgstr "阶梯温控模式"
|
||||
|
||||
msgid "手动定速模式"
|
||||
msgstr "手动定速模式"
|
||||
|
||||
msgid "内核接管模式"
|
||||
msgstr "内核接管模式"
|
||||
|
||||
msgid "处理器温度"
|
||||
msgstr "处理器温度"
|
||||
|
||||
msgid "当前风扇档位"
|
||||
msgstr "当前风扇档位"
|
||||
|
||||
msgid "脉宽占空比"
|
||||
msgstr "脉宽占空比"
|
||||
|
||||
msgid "0 级 [停转]"
|
||||
msgstr "0 级 [停转]"
|
||||
|
||||
msgid "1 级 [低速 31%]"
|
||||
msgstr "1 级 [低速 31%]"
|
||||
|
||||
msgid "2 级 [中速 50%]"
|
||||
msgstr "2 级 [中速 50%]"
|
||||
|
||||
msgid "3 级 [全速 100%]"
|
||||
msgstr "3 级 [全速 100%]"
|
||||
|
||||
msgid "级"
|
||||
msgstr "级"
|
||||
|
||||
msgid "硬件快速测试"
|
||||
msgstr "硬件快速测试"
|
||||
|
||||
msgid "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。"
|
||||
msgstr "点击按钮可即时下发档位进行硬件测试,测试结果立即生效并在状态栏回显。若服务处于运行中,后续温控检测将按设定规则恢复。"
|
||||
|
||||
msgid "停止风扇 (0 级)"
|
||||
msgstr "停止风扇 (0 级)"
|
||||
|
||||
msgid "低速测试 (1 级)"
|
||||
msgstr "低速测试 (1 级)"
|
||||
|
||||
msgid "中速测试 (2 级)"
|
||||
msgstr "中速测试 (2 级)"
|
||||
|
||||
msgid "全速测试 (3 级)"
|
||||
msgstr "全速测试 (3 级)"
|
||||
|
||||
msgid "正在下发测试档位: %d 级 ..."
|
||||
msgstr "正在下发测试档位: %d 级 ..."
|
||||
|
||||
msgid "风扇测试档位下发成功: %d 级"
|
||||
msgstr "风扇测试档位下发成功: %d 级"
|
||||
|
||||
msgid "风扇档位下发失败,请检查硬件节点。"
|
||||
msgstr "风扇档位下发失败,请检查硬件节点。"
|
||||
|
||||
msgid "正在执行服务操作: %s ..."
|
||||
msgstr "正在执行服务操作: %s ..."
|
||||
|
||||
msgid "服务操作成功执行。"
|
||||
msgstr "服务操作成功执行。"
|
||||
|
||||
msgid "服务操作执行失败。"
|
||||
msgstr "服务操作执行失败。"
|
||||
|
||||
msgid "RPC 请求失败: %s"
|
||||
msgstr "RPC 请求失败: %s"
|
||||
|
||||
msgid "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。"
|
||||
msgstr "配置香蕉派 BPI-R4 的 PWM 智能风扇调速策略与温度响应区间。"
|
||||
|
||||
msgid "参数设置"
|
||||
msgstr "参数设置"
|
||||
|
||||
msgid "启用服务"
|
||||
msgstr "启用服务"
|
||||
|
||||
msgid "开启风扇温控自动守护服务。"
|
||||
msgstr "开启风扇温控自动守护服务。"
|
||||
|
||||
msgid "工作模式"
|
||||
msgstr "工作模式"
|
||||
|
||||
msgid "选择风扇的控制策略模式。"
|
||||
msgstr "选择风扇的控制策略模式。"
|
||||
|
||||
msgid "检测周期 (秒)"
|
||||
msgstr "检测周期 (秒)"
|
||||
|
||||
msgid "温度轮询检测的时间间隔,推荐 3 到 10 秒。"
|
||||
msgstr "温度轮询检测的时间间隔,推荐 3 到 10 秒。"
|
||||
|
||||
msgid "启动自检清洁"
|
||||
msgstr "启动自检清洁"
|
||||
|
||||
msgid "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。"
|
||||
msgstr "服务启动时全速转动 2 秒,清洁积灰并确保轴承起步运转。"
|
||||
|
||||
msgid "手动常开档位"
|
||||
msgstr "手动常开档位"
|
||||
|
||||
msgid "指定风扇持续运转的固定档位。"
|
||||
msgstr "指定风扇持续运转的固定档位。"
|
||||
|
||||
msgid "0 级 [停转 0%]"
|
||||
msgstr "0 级 [停转 0%]"
|
||||
|
||||
msgid "1 级 [低速 31%]"
|
||||
msgstr "1 级 [低速 31%]"
|
||||
|
||||
msgid "2 级 [中速 50%]"
|
||||
msgstr "2 级 [中速 50%]"
|
||||
|
||||
msgid "3 级 [全速 100%]"
|
||||
msgstr "3 级 [全速 100%]"
|
||||
|
||||
msgid "1 级低速触发温度 (°C)"
|
||||
msgstr "1 级低速触发温度 (°C)"
|
||||
|
||||
msgid "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。"
|
||||
msgstr "温度达到此数值时启动 1 级低速 (约 31% 占空比),低于该值停转。"
|
||||
|
||||
msgid "2 级中速触发温度 (°C)"
|
||||
msgstr "2 级中速触发温度 (°C)"
|
||||
|
||||
msgid "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。"
|
||||
msgstr "温度达到此数值时提升至 2 级中速 (约 50% 占空比)。"
|
||||
|
||||
msgid "3 级全速触发温度 (°C)"
|
||||
msgstr "3 级全速触发温度 (°C)"
|
||||
|
||||
msgid "温度达到此数值时提升至 3 级全速 (100% 占空比)。"
|
||||
msgstr "温度达到此数值时提升至 3 级全速 (100% 占空比)。"
|
||||
|
||||
msgid "换挡回差温度 (°C)"
|
||||
msgstr "换挡回差温度 (°C)"
|
||||
|
||||
msgid "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。"
|
||||
msgstr "降温换挡的滞后温差。例如 60°C 升至中速,需降至 57°C 才会降回低速,防止临界点频繁启停。"
|
||||
|
||||
msgid "正在加载日志..."
|
||||
msgstr "正在加载日志..."
|
||||
|
||||
msgid "刷新日志"
|
||||
msgstr "刷新日志"
|
||||
|
||||
msgid "清空日志"
|
||||
msgstr "清空日志"
|
||||
|
||||
msgid "实时展示温控守护进程的调速动作、温度变化与自检日志。"
|
||||
msgstr "实时展示温控守护进程的调速动作、温度变化与自检日志。"
|
||||
|
||||
msgid "系统日志"
|
||||
msgstr "系统日志"
|
||||
|
||||
msgid "暂无日志记录。"
|
||||
msgstr "暂无日志记录。"
|
||||
|
||||
msgid "读取日志失败: %s"
|
||||
msgstr "读取日志失败: %s"
|
||||
|
||||
msgid "日志已成功清空。"
|
||||
msgstr "日志已成功清空。"
|
||||
|
||||
msgid "清空日志失败。"
|
||||
msgstr "清空日志失败。"
|
||||
|
||||
msgid "当前策略配置"
|
||||
msgstr "当前策略配置"
|
||||
|
||||
msgid "检测轮询周期"
|
||||
msgstr "检测轮询周期"
|
||||
|
||||
msgid "启动除尘自检"
|
||||
msgstr "启动除尘自检"
|
||||
|
||||
msgid "低速触发阈值"
|
||||
msgstr "低速触发阈值"
|
||||
|
||||
msgid "中速触发阈值"
|
||||
msgstr "中速触发阈值"
|
||||
|
||||
msgid "全速触发阈值"
|
||||
msgstr "全速触发阈值"
|
||||
|
||||
msgid "换挡回差温差"
|
||||
msgstr "换挡回差温差"
|
||||
|
||||
msgid "预设运转档位"
|
||||
msgstr "预设运转档位"
|
||||
|
||||
msgid "控制驱动策略"
|
||||
msgstr "控制驱动策略"
|
||||
|
||||
msgid "系统内核设备树原生驱动接管"
|
||||
msgstr "系统内核设备树原生驱动接管"
|
||||
|
||||
msgid "秒"
|
||||
msgstr "秒"
|
||||
|
||||
msgid "已开启 (开机全速 2 秒)"
|
||||
msgstr "已开启 (开机全速 2 秒)"
|
||||
|
||||
msgid "未开启"
|
||||
msgstr "未开启"
|
||||
|
||||
msgid "低于 %d °C 停转"
|
||||
msgstr "低于 %d °C 停转"
|
||||
|
||||
msgid "低于 %d °C 降回低速"
|
||||
msgstr "低于 %d °C 降回低速"
|
||||
|
||||
msgid "低于 %d °C 降回中速"
|
||||
msgstr "低于 %d °C 降回中速"
|
||||
|
||||
msgid "防临界点振荡"
|
||||
msgstr "防临界点振荡"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:80
|
||||
msgid "Temperature"
|
||||
msgstr "温度"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:140
|
||||
msgid "Temperature Curve"
|
||||
msgstr "温控曲线"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:226
|
||||
msgid "Temperature divisor"
|
||||
msgstr "温度除数"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:87
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:198
|
||||
msgid "Temperature input"
|
||||
msgstr "温度输入"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:156
|
||||
msgid ""
|
||||
"The fan remains off below this temperature and starts at the minimum running "
|
||||
"PWM when it is reached. Unit: °C."
|
||||
msgstr "低于此温度时风扇保持停止;达到此温度时,以最低运行 PWM 启动。单位为 °C。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:204
|
||||
msgid "Thermal zone type"
|
||||
msgstr "温度区域类型"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:202
|
||||
msgid ""
|
||||
"Use \"auto\" to select a CPU or SoC thermal sensor, or enter an absolute "
|
||||
"thermal zone or hwmon tempN_input path."
|
||||
msgstr "使用 \"auto\" 自动选择 CPU 或 SoC 温度传感器,也可输入 thermal zone 或 hwmon tempN_input 的绝对路径。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:213
|
||||
msgid ""
|
||||
"Use \"auto\" to select a writable hwmon pwmN output, or enter its absolute "
|
||||
"path. cooling_device cur_state is not a PWM output."
|
||||
msgstr "使用 \"auto\" 自动选择可写的 hwmon pwmN 输出,也可输入其绝对路径。cooling_device cur_state 不是 PWM 输出。"
|
||||
|
||||
#: /home/shi/document/c-service/fancontrol/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js:266
|
||||
msgid ""
|
||||
"Write temperature, PWM and RPM values to the system log at every polling "
|
||||
"interval."
|
||||
msgstr "每次轮询时将温度、PWM 和 RPM 数值写入系统日志。"
|
||||
@@ -1,10 +0,0 @@
|
||||
config fancontrol 'global'
|
||||
option enabled '1'
|
||||
option mode 'auto'
|
||||
option interval '5'
|
||||
option start_spin '1'
|
||||
option temp_low '45'
|
||||
option temp_med '60'
|
||||
option temp_high '75'
|
||||
option hysteresis '3'
|
||||
option manual_level '2'
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright (C) 2026 Dante <dante@seckv.com>
|
||||
|
||||
USE_PROCD=1
|
||||
START=95
|
||||
STOP=10
|
||||
|
||||
PROG="/usr/sbin/fancontrold"
|
||||
CONFIG="fancontrol"
|
||||
|
||||
start_service() {
|
||||
local enabled
|
||||
enabled=$(uci -q get "${CONFIG}.global.enabled" || echo 0)
|
||||
[ "${enabled}" = "1" ] || return 0
|
||||
|
||||
chmod 755 "${PROG}" 2>/dev/null
|
||||
|
||||
# 启动前清理任何可能残留的孤儿进程
|
||||
killall -q fancontrold 2>/dev/null
|
||||
|
||||
procd_open_instance "main"
|
||||
procd_set_param command "${PROG}"
|
||||
procd_set_param respawn 3600 5 0
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
killall -q fancontrold 2>/dev/null
|
||||
if [ -f "/sys/class/thermal/thermal_zone0/policy" ]; then
|
||||
echo "step_wise" > "/sys/class/thermal/thermal_zone0/policy" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "${CONFIG}"
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright (C) 2026 Dante <dante@seckv.com>
|
||||
|
||||
LOG_FILE="/var/log/fancontrol.log"
|
||||
CONFIG="fancontrol"
|
||||
|
||||
log_msg() {
|
||||
local level="$1"
|
||||
local msg="$2"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "${timestamp} [${level}] ${msg}" >> "${LOG_FILE}"
|
||||
|
||||
# 保持日志行数在 200 行以内
|
||||
if [ -f "${LOG_FILE}" ]; then
|
||||
local lines
|
||||
lines=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0)
|
||||
if [ "${lines}" -gt 200 ]; then
|
||||
local tmp_log
|
||||
tmp_log="$(tail -n 100 "${LOG_FILE}")"
|
||||
echo "${tmp_log}" > "${LOG_FILE}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 寻找硬件节点
|
||||
find_nodes() {
|
||||
THERMAL_ZONE=""
|
||||
COOLING_DEV=""
|
||||
HWMON_PWM=""
|
||||
|
||||
# 优先寻找 thermal_zone0
|
||||
if [ -f "/sys/class/thermal/thermal_zone0/temp" ]; then
|
||||
THERMAL_ZONE="/sys/class/thermal/thermal_zone0"
|
||||
else
|
||||
for z in /sys/class/thermal/thermal_zone*; do
|
||||
if [ -f "${z}/temp" ]; then
|
||||
THERMAL_ZONE="${z}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 寻找 pwm-fan 冷却设备
|
||||
for c in /sys/class/thermal/cooling_device*; do
|
||||
if [ -f "${c}/type" ]; then
|
||||
local ctype
|
||||
ctype=$(cat "${c}/type" 2>/dev/null)
|
||||
if [ "${ctype}" = "pwm-fan" ] || [ "${ctype}" = "pwmfan" ]; then
|
||||
COOLING_DEV="${c}"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 寻找 hwmon pwm 节点
|
||||
for h in /sys/class/hwmon/hwmon*; do
|
||||
if [ -f "${h}/name" ]; then
|
||||
local hname
|
||||
hname=$(cat "${h}/name" 2>/dev/null)
|
||||
if [ "${hname}" = "pwmfan" ] && [ -f "${h}/pwm1" ]; then
|
||||
HWMON_PWM="${h}/pwm1"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
get_temperature() {
|
||||
if [ -n "${THERMAL_ZONE}" ] && [ -f "${THERMAL_ZONE}/temp" ]; then
|
||||
cat "${THERMAL_ZONE}/temp" 2>/dev/null || echo "0"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
set_fan_level() {
|
||||
local lvl="$1"
|
||||
if [ -n "${COOLING_DEV}" ] && [ -w "${COOLING_DEV}/cur_state" ]; then
|
||||
echo "${lvl}" > "${COOLING_DEV}/cur_state" 2>/dev/null
|
||||
fi
|
||||
if [ -n "${HWMON_PWM}" ] && [ -w "${HWMON_PWM}" ]; then
|
||||
local pwm_val=0
|
||||
case "${lvl}" in
|
||||
0) pwm_val=0 ;;
|
||||
1) pwm_val=80 ;;
|
||||
2) pwm_val=128 ;;
|
||||
3) pwm_val=255 ;;
|
||||
*) pwm_val=0 ;;
|
||||
esac
|
||||
echo "${pwm_val}" > "${HWMON_PWM}" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
get_level_name() {
|
||||
case "$1" in
|
||||
0) echo "0 级 (停转)" ;;
|
||||
1) echo "1 级 (低速 31%)" ;;
|
||||
2) echo "2 级 (中速 50%)" ;;
|
||||
3) echo "3 级 (全速 100%)" ;;
|
||||
*) echo "$1 级" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
calc_level() {
|
||||
local temp="$1"
|
||||
local cur="$2"
|
||||
local l1="$3"
|
||||
local l2="$4"
|
||||
local l3="$5"
|
||||
local h="$6"
|
||||
|
||||
if [ "${cur}" -le 0 ]; then
|
||||
if [ "${temp}" -ge "${l3}" ]; then
|
||||
echo 3
|
||||
elif [ "${temp}" -ge "${l2}" ]; then
|
||||
echo 2
|
||||
elif [ "${temp}" -ge "${l1}" ]; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
elif [ "${cur}" -eq 1 ]; then
|
||||
if [ "${temp}" -ge "${l3}" ]; then
|
||||
echo 3
|
||||
elif [ "${temp}" -ge "${l2}" ]; then
|
||||
echo 2
|
||||
elif [ "${temp}" -lt "$((l1 - h))" ]; then
|
||||
echo 0
|
||||
else
|
||||
echo 1
|
||||
fi
|
||||
elif [ "${cur}" -eq 2 ]; then
|
||||
if [ "${temp}" -ge "${l3}" ]; then
|
||||
echo 3
|
||||
elif [ "${temp}" -lt "$((l1 - h))" ]; then
|
||||
echo 0
|
||||
elif [ "${temp}" -lt "$((l2 - h))" ]; then
|
||||
echo 1
|
||||
else
|
||||
echo 2
|
||||
fi
|
||||
else
|
||||
if [ "${temp}" -lt "$((l1 - h))" ]; then
|
||||
echo 0
|
||||
elif [ "${temp}" -lt "$((l2 - h))" ]; then
|
||||
echo 1
|
||||
elif [ "${temp}" -lt "$((l3 - h))" ]; then
|
||||
echo 2
|
||||
else
|
||||
echo 3
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then
|
||||
echo "step_wise" > "${THERMAL_ZONE}/policy" 2>/dev/null
|
||||
fi
|
||||
log_msg "信息" "风扇控制守护服务已停止,已恢复内核温控策略"
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup INT TERM QUIT
|
||||
|
||||
find_nodes
|
||||
|
||||
if [ -z "${COOLING_DEV}" ] && [ -z "${HWMON_PWM}" ]; then
|
||||
log_msg "错误" "未找到可用的风扇硬件控制节点,服务退出"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
enabled=$(uci -q get ${CONFIG}.global.enabled || echo 1)
|
||||
if [ "${enabled}" != "1" ]; then
|
||||
log_msg "信息" "服务配置为未启用状态,退出"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mode=$(uci -q get ${CONFIG}.global.mode || echo "auto")
|
||||
interval=$(uci -q get ${CONFIG}.global.interval || echo 5)
|
||||
start_spin=$(uci -q get ${CONFIG}.global.start_spin || echo 1)
|
||||
temp_low=$(uci -q get ${CONFIG}.global.temp_low || echo 45)
|
||||
temp_med=$(uci -q get ${CONFIG}.global.temp_med || echo 60)
|
||||
temp_high=$(uci -q get ${CONFIG}.global.temp_high || echo 75)
|
||||
hysteresis=$(uci -q get ${CONFIG}.global.hysteresis || echo 3)
|
||||
manual_level=$(uci -q get ${CONFIG}.global.manual_level || echo 2)
|
||||
|
||||
[ "${interval}" -lt 1 ] 2>/dev/null && interval=1
|
||||
[ "${interval}" -gt 60 ] 2>/dev/null && interval=60
|
||||
|
||||
log_msg "信息" "风扇控制守护服务启动,运行模式: ${mode},检测周期: ${interval} 秒"
|
||||
|
||||
# 启动自检
|
||||
if [ "${start_spin}" = "1" ] && [ "${mode}" != "kernel" ]; then
|
||||
log_msg "信息" "执行启动自检,全速运转 2 秒以清洁防尘"
|
||||
set_fan_level 3
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
# 非内核模式下切换为用户空间接管,防止内核 step_wise 依据 40°C 强制拉高转速
|
||||
if [ "${mode}" != "kernel" ] && [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then
|
||||
echo "user_space" > "${THERMAL_ZONE}/policy" 2>/dev/null
|
||||
fi
|
||||
|
||||
current_level=-1
|
||||
|
||||
while true; do
|
||||
case "${mode}" in
|
||||
"kernel")
|
||||
# 内核托管模式
|
||||
if [ -n "${THERMAL_ZONE}" ] && [ -w "${THERMAL_ZONE}/policy" ]; then
|
||||
echo "step_wise" > "${THERMAL_ZONE}/policy" 2>/dev/null
|
||||
fi
|
||||
sleep "${interval}"
|
||||
;;
|
||||
|
||||
"manual")
|
||||
if [ "${current_level}" -ne "${manual_level}" ]; then
|
||||
set_fan_level "${manual_level}"
|
||||
current_level="${manual_level}"
|
||||
lvl_name=$(get_level_name "${manual_level}")
|
||||
log_msg "信息" "手动定速模式生效,当前档位设定为: ${lvl_name}"
|
||||
fi
|
||||
sleep "${interval}"
|
||||
;;
|
||||
|
||||
"auto"|*)
|
||||
raw_t=$(get_temperature)
|
||||
# 单位毫摄氏度转整数摄氏度
|
||||
t_c=$((raw_t / 1000))
|
||||
t_dec=$(((raw_t % 1000) / 100))
|
||||
target_lvl=$(calc_level "${t_c}" "${current_level}" "${temp_low}" "${temp_med}" "${temp_high}" "${hysteresis}")
|
||||
|
||||
if [ "${target_lvl}" -ne "${current_level}" ]; then
|
||||
set_fan_level "${target_lvl}"
|
||||
old_name=$(get_level_name "${current_level}")
|
||||
new_name=$(get_level_name "${target_lvl}")
|
||||
if [ "${current_level}" -eq -1 ]; then
|
||||
log_msg "信息" "初始化风扇档位: 当前温度 ${t_c}.${t_dec} °C,设定为 ${new_name}"
|
||||
else
|
||||
log_msg "信息" "温度变化 (${t_c}.${t_dec} °C): 档位由 ${old_name} 调整为 ${new_name}"
|
||||
fi
|
||||
current_level="${target_lvl}"
|
||||
fi
|
||||
sleep "${interval}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
@@ -1,37 +1,13 @@
|
||||
{
|
||||
"admin/system/fancontrol": {
|
||||
"title": "风扇控制",
|
||||
"order": 65,
|
||||
"admin/services/fancontrol": {
|
||||
"title": "Fan Control",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
"type": "view",
|
||||
"path": "fancontrol"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-fancontrol" ],
|
||||
"uci": { "fancontrol": true }
|
||||
}
|
||||
},
|
||||
"admin/system/fancontrol/overview": {
|
||||
"title": "状态概览",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "fancontrol/overview"
|
||||
}
|
||||
},
|
||||
"admin/system/fancontrol/settings": {
|
||||
"title": "温控配置",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "fancontrol/settings"
|
||||
}
|
||||
},
|
||||
"admin/system/fancontrol/log": {
|
||||
"title": "运行日志",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "fancontrol/log"
|
||||
"acl": [ "luci-app-fancontrol" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"luci-app-fancontrol": {
|
||||
"description": "Grant access to BPI-R4 fan control procedures",
|
||||
"description": "Grant access to Fan Control",
|
||||
"read": {
|
||||
"uci": [ "fancontrol" ],
|
||||
"ubus": {
|
||||
"luci.fancontrol": [ "*" ]
|
||||
"service": [ "list" ]
|
||||
},
|
||||
"uci": [
|
||||
"fancontrol"
|
||||
]
|
||||
"file": {
|
||||
"/var/run/fancontrol.status": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.fancontrol": [ "*" ]
|
||||
},
|
||||
"uci": [
|
||||
"fancontrol"
|
||||
]
|
||||
"uci": [ "fancontrol" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import { access, readfile, writefile, stat, popen } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
import { connect } from 'ubus';
|
||||
|
||||
const UCI_CONFIG = 'fancontrol';
|
||||
const LOG_FILE = '/var/log/fancontrol.log';
|
||||
const THERMAL_ZONE_TEMP = '/sys/class/thermal/thermal_zone0/temp';
|
||||
const COOLING_DEV_CUR = '/sys/class/thermal/cooling_device0/cur_state';
|
||||
const HWMON_PWM = '/sys/class/hwmon/hwmon1/pwm1';
|
||||
|
||||
function get_procd_status() {
|
||||
let u = connect();
|
||||
if (!u) return { running: false, pid: null, state: 'stopped' };
|
||||
|
||||
let res = u.call('service', 'list', { name: 'fancontrol' });
|
||||
u.disconnect();
|
||||
|
||||
if (res && res['fancontrol'] && res['fancontrol'].instances) {
|
||||
let instances = res['fancontrol'].instances;
|
||||
for (let name in instances) {
|
||||
let inst = instances[name];
|
||||
if (inst && inst.running) {
|
||||
return { running: true, pid: inst.pid, state: 'managed' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有残留孤儿进程
|
||||
let p = popen('pidof /usr/sbin/fancontrold', 'r');
|
||||
if (p) {
|
||||
let pid_str = p.read('all');
|
||||
p.close();
|
||||
if (pid_str && length(trim(pid_str)) > 0) {
|
||||
let pids = split(trim(pid_str), /\s+/);
|
||||
if (length(pids) > 0 && pids[0] != '') {
|
||||
return { running: true, pid: int(pids[0]), state: 'unmanaged' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { running: false, pid: null, state: 'stopped' };
|
||||
}
|
||||
|
||||
function get_hardware_info() {
|
||||
let temp_c = 0.0;
|
||||
if (access(THERMAL_ZONE_TEMP, 'r')) {
|
||||
let raw_t = readfile(THERMAL_ZONE_TEMP);
|
||||
if (raw_t) {
|
||||
let raw_val = int(trim(raw_t));
|
||||
temp_c = sprintf('%.1f', raw_val / 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
let cur_level = 0;
|
||||
if (access(COOLING_DEV_CUR, 'r')) {
|
||||
let raw_lvl = readfile(COOLING_DEV_CUR);
|
||||
if (raw_lvl) cur_level = int(trim(raw_lvl));
|
||||
}
|
||||
|
||||
let pwm_val = 0;
|
||||
if (access(HWMON_PWM, 'r')) {
|
||||
let raw_pwm = readfile(HWMON_PWM);
|
||||
if (raw_pwm) pwm_val = int(trim(raw_pwm));
|
||||
} else {
|
||||
// 依据档位估算
|
||||
if (cur_level == 1) pwm_val = 80;
|
||||
else if (cur_level == 2) pwm_val = 128;
|
||||
else if (cur_level == 3) pwm_val = 255;
|
||||
}
|
||||
|
||||
return {
|
||||
temp: temp_c,
|
||||
level: cur_level,
|
||||
pwm: pwm_val
|
||||
};
|
||||
}
|
||||
|
||||
const methods = {
|
||||
get_status: {
|
||||
call: function() {
|
||||
let proc = get_procd_status();
|
||||
let hw = get_hardware_info();
|
||||
let uci_c = cursor();
|
||||
let enabled = int(uci_c.get(UCI_CONFIG, 'global', 'enabled') || '0');
|
||||
let mode = uci_c.get(UCI_CONFIG, 'global', 'mode') || 'auto';
|
||||
let interval = int(uci_c.get(UCI_CONFIG, 'global', 'interval') || '5');
|
||||
|
||||
let start_spin = int(uci_c.get(UCI_CONFIG, 'global', 'start_spin') || '1');
|
||||
let temp_low = int(uci_c.get(UCI_CONFIG, 'global', 'temp_low') || '45');
|
||||
let temp_med = int(uci_c.get(UCI_CONFIG, 'global', 'temp_med') || '60');
|
||||
let temp_high = int(uci_c.get(UCI_CONFIG, 'global', 'temp_high') || '75');
|
||||
let hysteresis = int(uci_c.get(UCI_CONFIG, 'global', 'hysteresis') || '3');
|
||||
let manual_level = int(uci_c.get(UCI_CONFIG, 'global', 'manual_level') || '2');
|
||||
|
||||
return {
|
||||
running: proc.running,
|
||||
state: proc.state,
|
||||
pid: proc.pid,
|
||||
enabled: enabled,
|
||||
mode: mode,
|
||||
interval: interval,
|
||||
start_spin: start_spin,
|
||||
temp_low: temp_low,
|
||||
temp_med: temp_med,
|
||||
temp_high: temp_high,
|
||||
hysteresis: hysteresis,
|
||||
manual_level: manual_level,
|
||||
temp: hw.temp,
|
||||
level: hw.level,
|
||||
pwm: hw.pwm
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
set_level: {
|
||||
args: { level: 0 },
|
||||
call: function(req) {
|
||||
let lvl = int(req.args?.level ?? 0);
|
||||
if (lvl < 0) lvl = 0;
|
||||
if (lvl > 3) lvl = 3;
|
||||
|
||||
let applied = false;
|
||||
if (access(COOLING_DEV_CUR, 'w')) {
|
||||
try {
|
||||
writefile(COOLING_DEV_CUR, lvl + "\n");
|
||||
applied = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!applied && access(HWMON_PWM, 'w')) {
|
||||
let pwm_val = 0;
|
||||
if (lvl == 1) pwm_val = 80;
|
||||
else if (lvl == 2) pwm_val = 128;
|
||||
else if (lvl == 3) pwm_val = 255;
|
||||
try {
|
||||
writefile(HWMON_PWM, pwm_val + "\n");
|
||||
applied = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 记录测试日志
|
||||
try {
|
||||
let p = popen('date "+%Y-%m-%d %H:%M:%S"', 'r');
|
||||
let ts = p ? trim(p.read('all')) : '';
|
||||
if (p) p.close();
|
||||
let line = ts + ' [测试] 手动即时下发风扇档位: ' + lvl + ' 级\n';
|
||||
let old_content = readfile(LOG_FILE) || '';
|
||||
writefile(LOG_FILE, old_content + line);
|
||||
} catch (e) {}
|
||||
|
||||
return { success: applied, level: lvl };
|
||||
}
|
||||
},
|
||||
|
||||
get_logs: {
|
||||
args: { lines: 100 },
|
||||
call: function(req) {
|
||||
let lines_limit = int(req.args?.lines ?? 100);
|
||||
let logs = [];
|
||||
try {
|
||||
if (access(LOG_FILE, 'r')) {
|
||||
let log_stat = stat(LOG_FILE);
|
||||
let content = '';
|
||||
if (log_stat && log_stat.size > 65536) {
|
||||
let fd = popen('tail -c 65536 ' + LOG_FILE, 'r');
|
||||
if (fd) {
|
||||
content = fd.read('all');
|
||||
fd.close();
|
||||
}
|
||||
} else {
|
||||
content = readfile(LOG_FILE);
|
||||
}
|
||||
if (content) {
|
||||
let all_lines = split(content, "\n");
|
||||
let total = length(all_lines);
|
||||
let start = (total > lines_limit) ? (total - lines_limit) : 0;
|
||||
for (let i = start; i < total; i++) {
|
||||
if (all_lines[i] && length(trim(all_lines[i])) > 0) {
|
||||
push(logs, all_lines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return { logs: logs };
|
||||
}
|
||||
},
|
||||
|
||||
clear_logs: {
|
||||
call: function() {
|
||||
try {
|
||||
writefile(LOG_FILE, '');
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { success: false, error: e };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
service: {
|
||||
args: { action: 'restart' },
|
||||
call: function(req) {
|
||||
let action = req.args?.action || 'restart';
|
||||
if (action != 'start' && action != 'stop' && action != 'restart') {
|
||||
return { success: false, error: 'Invalid action' };
|
||||
}
|
||||
|
||||
if (action == 'restart' || action == 'stop') {
|
||||
// 强杀孤儿残留进程
|
||||
let p = popen('killall -9 fancontrold', 'r');
|
||||
if (p) p.close();
|
||||
}
|
||||
|
||||
let rc = system('/etc/init.d/fancontrol ' + action + ' >/dev/null 2>&1');
|
||||
system('sleep 1');
|
||||
return { success: (rc === 0) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.fancontrol': methods };
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-gately
|
||||
PKG_VERSION:=0.6.6
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=0.6.6.2
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_MAINTAINER:=wangwanjie <https://github.com/wangwanjie/gately>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
File renamed without changes.
@@ -6,17 +6,17 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for v2rayA
|
||||
LUCI_TITLE:=LuCI support for v2rayA (NextGen)
|
||||
LUCI_DEPENDS:=+luci-base +v2raya
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_MAINTAINER:=Amr Fateem <amr.fateem@yahoo.com>
|
||||
|
||||
# Date-based version (YY.DDD), the same scheme as the OpenWrt LuCI packages.
|
||||
# OpenWrt's luci-app-v2raya is newer than 2.5.13 by version comparison, so
|
||||
# this version must stay ahead of it. Re-date it when OpenWrt passes it.
|
||||
PKG_VERSION:=26.317.1
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
CONFLICTS:=luci-app-v2raya-nextgen
|
||||
CONFLICTS:=luci-app-v2raya
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=v2raya
|
||||
PKG_VERSION:=2.5.15
|
||||
PKG_RELEASE:=12
|
||||
PKG_RELEASE:=13
|
||||
|
||||
PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -80,7 +80,7 @@ version_lt() {
|
||||
old_v2raya_pkg=0
|
||||
pkg_has v2raya-nextgen && old_v2raya_pkg=1
|
||||
pkg_has v2raya-core-nextgen && old_v2raya_pkg=1
|
||||
pkg_has luci-app-v2raya-nextgen && old_v2raya_pkg=1
|
||||
pkg_has luci-app-v2raya && old_v2raya_pkg=1
|
||||
if pkg_has v2raya; then
|
||||
version_lt "$(pkg_version v2raya)" "2.5" && old_v2raya_pkg=1
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user