🌈 Sync 2026-11-13 03:33:44

This commit is contained in:
github-actions[bot] committed 2026-11-13 03:33:44 +08:00
1 parent 4fe4df083c
commit 82323be37b
37 files changed
+938 -6332

No files matched your search

+13 -40
View File
@@ -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:=5
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:=6
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 ""
+278 -246
View File
@@ -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 };
-25
View File
@@ -1,25 +0,0 @@
#
# Copyright 2025 Rafał Wabik - IceG - From eko.one.pl forum
#
# MIT License
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ports-status-mod
PKG_MAINTAINER:=Rafał Wabik <4Rafal@gmail.com>
LUCI_TITLE:=Small modification of Ports Status
LUCI_DEPENDS:=+luci-mod-admin-full
LUCI_PKGARCH:=all
PKG_LICENSE:=MIT
PKG_VERSION:=1.0.8
PKG_RELEASE:=1
define Package/luci-app-ports-status-mod/conffiles
/etc/user_defined_ports.json
/etc/user_defined_ports.json.bak
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: cs\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n"
msgid "Edit Port Label"
msgstr "Upravit štítek portu"
msgid "Enter new label for this port:"
msgstr "Zadejte nový štítek pro tento port:"
msgid "Device"
msgstr "Zařízení"
msgid "Original label"
msgstr "Původní štítek"
msgid "Restore Original"
msgstr "Obnovit původní"
msgid "Cancel"
msgstr "Zrušit"
msgid "Save"
msgstr "Uložit"
msgid "Port configuration saved successfully"
msgstr "Konfigurace portu byla úspěšně uložena"
msgid "Port order saved"
msgstr "Pořadí portů bylo uloženo"
msgid "Click to edit label"
msgstr "Kliknutím upravíte štítek"
msgid "Hold to drag and reorder"
msgstr "Podržením přetáhněte a změňte pořadí"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Štítek maximálně 9 znaků, popis maximálně 50 znaků."
msgid "Enter description (optional):"
msgstr "Zadejte popis (volitelné):"
msgid "Optional description"
msgstr "Volitelný popis"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Uživatelská nastavení jsou ukládána do souboru /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Konfigurační soubor byl stažen"
msgid "Configuration file not found"
msgstr "Konfigurační soubor nebyl nalezen"
msgid "Download error: %s"
msgstr "Chyba stahování: %s"
msgid "Invalid configuration format"
msgstr "Neplatný formát konfigurace"
msgid "Restore configuration"
msgstr "Obnovit konfiguraci"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Tato operace přepíše aktuální konfiguraci portů. Pokračovat?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Konfigurace byla úspěšně obnovena. Načítání..."
msgid "File restore failed: %s"
msgstr "Obnovení souboru selhalo: %s"
msgid "Save .json file"
msgstr "Uložit soubor .json"
msgid "Upload .json file"
msgstr "Nahrát soubor .json"
msgid "Cannot save empty configuration"
msgstr "Nelze uložit prázdnou konfiguraci"
msgid "Cannot save port configuration. "
msgstr "Nelze uložit konfiguraci portu. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Adresář /etc může být pouze pro čtení nebo má nedostatečná oprávnění. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Zkuste spustit: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Chyba ukládání"
msgid "Original error: %s"
msgstr "Původní chyba: %s"
msgid "Restore from backup"
msgstr "Obnovit ze zálohy"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Obnovit konfiguraci ze záložního souboru? Tímto dojde k přepsání aktuálního nastavení."
msgid "Backup contains:"
msgstr "Záloha obsahuje:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Konfigurace byla obnovena ze zálohy. Načítání..."
msgid "Restore failed: %s"
msgstr "Obnovení selhalo: %s"
msgid "Backup file is corrupted: %s"
msgstr "Záložní soubor je poškozen: %s"
msgid "Error reading backup: %s"
msgstr "Chyba při čtení zálohy: %s"
msgid "Restore backup .bak"
msgstr "Obnovit zálohu .bak"
msgid "Change port state"
msgstr "Změnit stav portu"
msgid "Enable this port"
msgstr "Povolit tento port"
msgid "Enable port"
msgstr "Povolit port"
msgid "Disable this port"
msgstr "Zakázat tento port"
msgid "Disable port"
msgstr "Zakázat port"
msgid "Down"
msgstr "neaktivní"
msgid "Disabled"
msgstr "zakázáno"
msgid "Idle"
msgstr "nečinný"
msgid "Active"
msgstr "aktivní"
msgid "Unknown"
msgstr "neznámý"
msgid "No config file to backup"
msgstr "Žádný konfigurační soubor k zálohování"
msgid "Config file is corrupted, cannot create backup"
msgstr "Konfigurační soubor je poškozen, nelze vytvořit zálohu"
msgid "Backup file created: %s"
msgstr "Záložní soubor byl vytvořen: %s"
msgid "Backup write verification failed!"
msgstr "Ověření zápisu zálohy selhalo!"
msgid "Backup creation failed: %s"
msgstr "Vytvoření zálohy selhalo: %s"
msgid "Error reading config: %s"
msgstr "Chyba při čtení konfigurace: %s"
msgid "Create backup .bak"
msgstr "Vytvořit zálohu .bak"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: da\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "Edit Port Label"
msgstr "Rediger portmærkat"
msgid "Enter new label for this port:"
msgstr "Indtast ny mærkat for denne port:"
msgid "Device"
msgstr "Enhed"
msgid "Original label"
msgstr "Oprindelig mærkat"
msgid "Restore Original"
msgstr "Gendan oprindelig"
msgid "Cancel"
msgstr "Annuller"
msgid "Save"
msgstr "Gem"
msgid "Port configuration saved successfully"
msgstr "Portkonfigurationen blev gemt"
msgid "Port order saved"
msgstr "Portrækkefølge gemt"
msgid "Click to edit label"
msgstr "Klik for at redigere mærkat"
msgid "Hold to drag and reorder"
msgstr "Hold nede for at trække og omarrangere"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Mærkat maks. 9 tegn, beskrivelse maks. 50 tegn."
msgid "Enter description (optional):"
msgstr "Indtast beskrivelse (valgfri):"
msgid "Optional description"
msgstr "Valgfri beskrivelse"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Brugerindstillinger gemmes i filen /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Konfigurationsfil downloadet"
msgid "Configuration file not found"
msgstr "Konfigurationsfil ikke fundet"
msgid "Download error: %s"
msgstr "Downloadfejl: %s"
msgid "Invalid configuration format"
msgstr "Ugyldigt konfigurationsformat"
msgid "Restore configuration"
msgstr "Gendan konfiguration"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Dette vil overskrive den nuværende portkonfiguration. Fortsæt?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Konfigurationen blev gendannet. Genindlæser..."
msgid "File restore failed: %s"
msgstr "Gendannelse af fil mislykkedes: %s"
msgid "Save .json file"
msgstr "Gem .json-fil"
msgid "Upload .json file"
msgstr "Upload .json-fil"
msgid "Cannot save empty configuration"
msgstr "Kan ikke gemme en tom konfiguration"
msgid "Cannot save port configuration. "
msgstr "Kan ikke gemme portkonfiguration. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Mappen /etc er muligvis skrivebeskyttet eller har utilstrækkelige tilladelser. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Prøv at køre: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Fejl ved lagring"
msgid "Original error: %s"
msgstr "Oprindelig fejl: %s"
msgid "Restore from backup"
msgstr "Gendan fra sikkerhedskopi"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Gendan konfiguration fra sikkerhedskopifil? Dette vil overskrive de nuværende indstillinger."
msgid "Backup contains:"
msgstr "Sikkerhedskopi indeholder:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Konfiguration gendannet fra sikkerhedskopi. Genindlæser..."
msgid "Restore failed: %s"
msgstr "Gendannelse mislykkedes: %s"
msgid "Backup file is corrupted: %s"
msgstr "Sikkerhedskopifilen er beskadiget: %s"
msgid "Error reading backup: %s"
msgstr "Fejl ved læsning af sikkerhedskopi: %s"
msgid "Restore backup .bak"
msgstr "Gendan .bak-sikkerhedskopi"
msgid "Change port state"
msgstr "Skift portstatus"
msgid "Enable this port"
msgstr "Aktiver denne port"
msgid "Enable port"
msgstr "Aktiver port"
msgid "Disable this port"
msgstr "Deaktiver denne port"
msgid "Disable port"
msgstr "Deaktiver port"
msgid "Down"
msgstr "nede"
msgid "Disabled"
msgstr "deaktiveret"
msgid "Idle"
msgstr "inaktiv"
msgid "Active"
msgstr "aktiv"
msgid "Unknown"
msgstr "ukendt"
msgid "No config file to backup"
msgstr "Ingen konfigurationsfil at sikkerhedskopiere"
msgid "Config file is corrupted, cannot create backup"
msgstr "Konfigurationsfilen er beskadiget, kan ikke oprette sikkerhedskopi"
msgid "Backup file created: %s"
msgstr "Sikkerhedskopifil oprettet: %s"
msgid "Backup write verification failed!"
msgstr "Skriveverifikation af sikkerhedskopi mislykkedes!"
msgid "Backup creation failed: %s"
msgstr "Oprettelse af sikkerhedskopi mislykkedes: %s"
msgid "Error reading config: %s"
msgstr "Fejl ved læsning af konfiguration: %s"
msgid "Create backup .bak"
msgstr "Opret .bak-sikkerhedskopi"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: de\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "Edit Port Label"
msgstr "Port-Beschriftung bearbeiten"
msgid "Enter new label for this port:"
msgstr "Neue Beschriftung für diesen Port eingeben:"
msgid "Device"
msgstr "Gerät"
msgid "Original label"
msgstr "Originalbeschriftung"
msgid "Restore Original"
msgstr "Original wiederherstellen"
msgid "Cancel"
msgstr "Abbrechen"
msgid "Save"
msgstr "Speichern"
msgid "Port configuration saved successfully"
msgstr "Port-Konfiguration erfolgreich gespeichert"
msgid "Port order saved"
msgstr "Port-Reihenfolge gespeichert"
msgid "Click to edit label"
msgstr "Klicken Sie, um die Beschriftung zu bearbeiten"
msgid "Hold to drag and reorder"
msgstr "Halten Sie gedrückt, um zu ziehen und neu anzuordnen"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Beschriftung max. 9 Zeichen, Beschreibung max. 50 Zeichen."
msgid "Enter description (optional):"
msgstr "Beschreibung eingeben (optional):"
msgid "Optional description"
msgstr "Optionale Beschreibung"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Benutzereinstellungen werden in der Datei /etc/user_defined_ports.json gespeichert."
msgid "Configuration file downloaded"
msgstr "Konfigurationsdatei heruntergeladen"
msgid "Configuration file not found"
msgstr "Konfigurationsdatei nicht gefunden"
msgid "Download error: %s"
msgstr "Download-Fehler: %s"
msgid "Invalid configuration format"
msgstr "Ungültiges Konfigurationsformat"
msgid "Restore configuration"
msgstr "Konfiguration wiederherstellen"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Dies überschreibt die aktuelle Port-Konfiguration. Fortfahren?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Konfiguration erfolgreich wiederhergestellt. Wird neu geladen..."
msgid "File restore failed: %s"
msgstr "Dateiwiederherstellung fehlgeschlagen: %s"
msgid "Save .json file"
msgstr ".json-Datei speichern"
msgid "Upload .json file"
msgstr ".json-Datei hochladen"
msgid "Cannot save empty configuration"
msgstr "Leere Konfiguration kann nicht gespeichert werden"
msgid "Cannot save port configuration. "
msgstr "Port-Konfiguration kann nicht gespeichert werden. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Verzeichnis /etc ist möglicherweise schreibgeschützt oder hat unzureichende Berechtigungen. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Versuchen Sie auszuführen: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Speicherfehler"
msgid "Original error: %s"
msgstr "Ursprünglicher Fehler: %s"
msgid "Restore from backup"
msgstr "Aus Sicherung wiederherstellen"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Konfiguration aus Sicherungsdatei wiederherstellen? Dies überschreibt die aktuellen Einstellungen."
msgid "Backup contains:"
msgstr "Sicherung enthält:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Konfiguration aus Sicherung wiederhergestellt. Wird neu geladen..."
msgid "Restore failed: %s"
msgstr "Wiederherstellung fehlgeschlagen: %s"
msgid "Backup file is corrupted: %s"
msgstr "Sicherungsdatei ist beschädigt: %s"
msgid "Error reading backup: %s"
msgstr "Fehler beim Lesen der Sicherung: %s"
msgid "Restore backup .bak"
msgstr "Sicherung .bak wiederherstellen"
msgid "Change port state"
msgstr "Portstatus ändern"
msgid "Enable this port"
msgstr "Diesen Port aktivieren"
msgid "Enable port"
msgstr "Port aktivieren"
msgid "Disable this port"
msgstr "Diesen Port deaktivieren"
msgid "Disable port"
msgstr "Port deaktivieren"
msgid "Down"
msgstr "inaktiv"
msgid "Disabled"
msgstr "deaktiviert"
msgid "Idle"
msgstr "leerlauf"
msgid "Active"
msgstr "aktiv"
msgid "Unknown"
msgstr "unbekannt"
msgid "No config file to backup"
msgstr "Keine Konfigurationsdatei zum Sichern vorhanden"
msgid "Config file is corrupted, cannot create backup"
msgstr "Konfigurationsdatei ist beschädigt, Sicherung kann nicht erstellt werden"
msgid "Backup file created: %s"
msgstr "Sicherungsdatei erstellt: %s"
msgid "Backup write verification failed!"
msgstr "Überprüfung des Sicherungsschreibvorgangs fehlgeschlagen!"
msgid "Backup creation failed: %s"
msgstr "Erstellung der Sicherung fehlgeschlagen: %s"
msgid "Error reading config: %s"
msgstr "Fehler beim Lesen der Konfiguration: %s"
msgid "Create backup .bak"
msgstr "Sicherung .bak erstellen"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: es\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "Edit Port Label"
msgstr "Editar etiqueta del puerto"
msgid "Enter new label for this port:"
msgstr "Introduzca la nueva etiqueta para este puerto:"
msgid "Device"
msgstr "Dispositivo"
msgid "Original label"
msgstr "Etiqueta original"
msgid "Restore Original"
msgstr "Restaurar original"
msgid "Cancel"
msgstr "Cancelar"
msgid "Save"
msgstr "Guardar"
msgid "Port configuration saved successfully"
msgstr "Configuración del puerto guardada correctamente"
msgid "Port order saved"
msgstr "Orden de los puertos guardado"
msgid "Click to edit label"
msgstr "Haga clic para editar la etiqueta"
msgid "Hold to drag and reorder"
msgstr "Mantenga pulsado para arrastrar y reordenar"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Etiqueta máx. 9 caracteres, descripción máx. 50 caracteres."
msgid "Enter description (optional):"
msgstr "Introduzca una descripción (opcional):"
msgid "Optional description"
msgstr "Descripción opcional"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Los ajustes de usuario se guardan en el archivo /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Archivo de configuración descargado"
msgid "Configuration file not found"
msgstr "Archivo de configuración no encontrado"
msgid "Download error: %s"
msgstr "Error de descarga: %s"
msgid "Invalid configuration format"
msgstr "Formato de configuración no válido"
msgid "Restore configuration"
msgstr "Restaurar configuración"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Esta acción sobrescribirá la configuración actual de los puertos. ¿Continuar?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Configuración restaurada correctamente. Recargando..."
msgid "File restore failed: %s"
msgstr "Fallo al restaurar el archivo: %s"
msgid "Save .json file"
msgstr "Guardar archivo .json"
msgid "Upload .json file"
msgstr "Subir archivo .json"
msgid "Cannot save empty configuration"
msgstr "No se puede guardar una configuración vacía"
msgid "Cannot save port configuration. "
msgstr "No se puede guardar la configuración del puerto. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "El directorio /etc puede ser de solo lectura o no tener permisos suficientes. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Intente ejecutar: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Error al guardar"
msgid "Original error: %s"
msgstr "Error original: %s"
msgid "Restore from backup"
msgstr "Restaurar desde copia de seguridad"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "¿Restaurar configuración desde el archivo de copia de seguridad? Se sobrescribirán los ajustes actuales."
msgid "Backup contains:"
msgstr "La copia de seguridad contiene:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Configuración restaurada desde la copia de seguridad. Recargando..."
msgid "Restore failed: %s"
msgstr "Fallo al restaurar: %s"
msgid "Backup file is corrupted: %s"
msgstr "El archivo de copia de seguridad está dañado: %s"
msgid "Error reading backup: %s"
msgstr "Error al leer la copia de seguridad: %s"
msgid "Restore backup .bak"
msgstr "Restaurar copia .bak"
msgid "Change port state"
msgstr "Cambiar estado del puerto"
msgid "Enable this port"
msgstr "Habilitar este puerto"
msgid "Enable port"
msgstr "Habilitar puerto"
msgid "Disable this port"
msgstr "Deshabilitar este puerto"
msgid "Disable port"
msgstr "Deshabilitar puerto"
msgid "Down"
msgstr "caído"
msgid "Disabled"
msgstr "deshabilitado"
msgid "Idle"
msgstr "inactivo"
msgid "Active"
msgstr "activo"
msgid "Unknown"
msgstr "desconocido"
msgid "No config file to backup"
msgstr "No hay archivo de configuración para respaldar"
msgid "Config file is corrupted, cannot create backup"
msgstr "El archivo de configuración está dañado, no se puede crear la copia de seguridad"
msgid "Backup file created: %s"
msgstr "Archivo de copia de seguridad creado: %s"
msgid "Backup write verification failed!"
msgstr "¡Falló la verificación de escritura de la copia de seguridad!"
msgid "Backup creation failed: %s"
msgstr "Fallo al crear la copia de seguridad: %s"
msgid "Error reading config: %s"
msgstr "Error al leer la configuración: %s"
msgid "Create backup .bak"
msgstr "Crear copia .bak"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: fr\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
msgid "Edit Port Label"
msgstr "Modifier l'étiquette du port"
msgid "Enter new label for this port:"
msgstr "Entrez une nouvelle étiquette pour ce port :"
msgid "Device"
msgstr "Périphérique"
msgid "Original label"
msgstr "Étiquette d'origine"
msgid "Restore Original"
msgstr "Restaurer l'original"
msgid "Cancel"
msgstr "Annuler"
msgid "Save"
msgstr "Enregistrer"
msgid "Port configuration saved successfully"
msgstr "Configuration du port enregistrée avec succès"
msgid "Port order saved"
msgstr "Ordre des ports enregistré"
msgid "Click to edit label"
msgstr "Cliquez pour modifier l'étiquette"
msgid "Hold to drag and reorder"
msgstr "Maintenez enfoncé pour faire glisser et réorganiser"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Étiquette max. 9 caractères, description max. 50 caractères."
msgid "Enter description (optional):"
msgstr "Entrez la description (facultatif) :"
msgid "Optional description"
msgstr "Description facultative"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Les paramètres utilisateur sont enregistrés dans le fichier /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Fichier de configuration téléchargé"
msgid "Configuration file not found"
msgstr "Fichier de configuration introuvable"
msgid "Download error: %s"
msgstr "Erreur de téléchargement : %s"
msgid "Invalid configuration format"
msgstr "Format de configuration non valide"
msgid "Restore configuration"
msgstr "Restaurer la configuration"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Cela écrasera la configuration actuelle des ports. Continuer ?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Configuration restaurée avec succès. Rechargement..."
msgid "File restore failed: %s"
msgstr "Échec de la restauration du fichier : %s"
msgid "Save .json file"
msgstr "Enregistrer le fichier .json"
msgid "Upload .json file"
msgstr "Téléverser le fichier .json"
msgid "Cannot save empty configuration"
msgstr "Impossible d'enregistrer une configuration vide"
msgid "Cannot save port configuration. "
msgstr "Impossible d'enregistrer la configuration des ports. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Le répertoire /etc est peut-être en lecture seule ou les permissions sont insuffisantes. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Essayez d'exécuter : chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Erreur d'enregistrement"
msgid "Original error: %s"
msgstr "Erreur d'origine : %s"
msgid "Restore from backup"
msgstr "Restaurer à partir de la sauvegarde"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Restaurer la configuration à partir du fichier de sauvegarde ? Cela écrasera les paramètres actuels."
msgid "Backup contains:"
msgstr "La sauvegarde contient :"
msgid "Configuration restored from backup. Reloading..."
msgstr "Configuration restaurée à partir de la sauvegarde. Rechargement..."
msgid "Restore failed: %s"
msgstr "Échec de la restauration : %s"
msgid "Backup file is corrupted: %s"
msgstr "Le fichier de sauvegarde est corrompu : %s"
msgid "Error reading backup: %s"
msgstr "Erreur de lecture de la sauvegarde : %s"
msgid "Restore backup .bak"
msgstr "Restaurer la sauvegarde .bak"
msgid "Change port state"
msgstr "Changer l'état du port"
msgid "Enable this port"
msgstr "Activer ce port"
msgid "Enable port"
msgstr "Activer le port"
msgid "Disable this port"
msgstr "Désactiver ce port"
msgid "Disable port"
msgstr "Désactiver le port"
msgid "Down"
msgstr "inactif"
msgid "Disabled"
msgstr "désactivé"
msgid "Idle"
msgstr "inactif"
msgid "Active"
msgstr "actif"
msgid "Unknown"
msgstr "inconnu"
msgid "No config file to backup"
msgstr "Aucun fichier de configuration à sauvegarder"
msgid "Config file is corrupted, cannot create backup"
msgstr "Le fichier de configuration est corrompu, impossible de créer une sauvegarde"
msgid "Backup file created: %s"
msgstr "Fichier de sauvegarde créé : %s"
msgid "Backup write verification failed!"
msgstr "Échec de la vérification de l'écriture de la sauvegarde !"
msgid "Backup creation failed: %s"
msgstr "Échec de la création de la sauvegarde : %s"
msgid "Error reading config: %s"
msgstr "Erreur lors de la lecture de la configuration : %s"
msgid "Create backup .bak"
msgstr "Créer une sauvegarde .bak"
@@ -1,122 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Edit Port Label"
msgstr "Edit Label Port"
msgid "Enter new label for this port:"
msgstr "Masukkan label baru untuk port ini:"
msgid "Device"
msgstr "Perangkat"
msgid "Original label"
msgstr "Label asli"
msgid "Restore Original"
msgstr "Pulihkan Asli"
msgid "Cancel"
msgstr "Batal"
msgid "Save"
msgstr "Simpan"
msgid "Port configuration saved successfully"
msgstr "Konfigurasi port berhasil disimpan"
msgid "Port order saved"
msgstr "Urutan port tersimpan"
msgid "Click to edit label"
msgstr "Klik untuk mengedit label"
msgid "Hold to drag and reorder"
msgstr "Tahan untuk menyeret dan mengurutkan ulang"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Label maks. 9 karakter, deskripsi maks. 50 karakter."
msgid "Enter description (optional):"
msgstr "Masukkan deskripsi (opsional):"
msgid "Optional description"
msgstr "Deskripsi opsional"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Pengaturan pengguna disimpan ke file /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "File konfigurasi berhasil diunduh"
msgid "Configuration file not found"
msgstr "File konfigurasi tidak ditemukan"
msgid "Download error: %s"
msgstr "Kesalahan unduh: %s"
msgid "Invalid configuration format"
msgstr "Format konfigurasi tidak valid"
msgid "Restore configuration"
msgstr "Pulihkan konfigurasi"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Ini akan menimpa konfigurasi port saat ini. Lanjutkan?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Konfigurasi berhasil dipulihkan. Memuat ulang..."
msgid "File restore failed: %s"
msgstr "Pemulihan file gagal: %s"
msgid "Save .json file"
msgstr "Simpan file .json"
msgid "Upload .json file"
msgstr "Unggah file .json"
msgid "Cannot save empty configuration"
msgstr "Tidak dapat menyimpan konfigurasi kosong"
msgid "Cannot save port configuration. "
msgstr "Tidak dapat menyimpan konfigurasi port. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Direktori /etc mungkin hanya-baca atau izin tidak mencukupi. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Coba jalankan: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Kesalahan Penyimpanan"
msgid "Original error: %s"
msgstr "Kesalahan asli: %s"
msgid "Restore from backup"
msgstr "Pulihkan dari cadangan"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Pulihkan konfigurasi dari file cadangan? Ini akan menimpa pengaturan saat ini."
msgid "Backup contains:"
msgstr "Cadangan berisi:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Konfigurasi dipulihkan dari cadangan. Memuat ulang..."
msgid "Restore failed: %s"
msgstr "Pemulihan gagal: %s"
msgid "Backup file is corrupted: %s"
msgstr "File cadangan rusak: %s"
msgid "Error reading backup: %s"
msgstr "Kesalahan membaca cadangan: %s"
msgid "Restore backup .bak"
msgstr "Pulihkan cadangan .bak"
msgid ""
msgstr ""
@@ -1,176 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: lt\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"(n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "Edit Port Label"
msgstr "Modifica etichetta porta"
msgid "Enter new label for this port:"
msgstr "Inserisci una nuova etichetta per questa porta:"
msgid "Device"
msgstr "Dispositivo"
msgid "Original label"
msgstr "Etichetta originale"
msgid "Restore Original"
msgstr "Ripristina originale"
msgid "Cancel"
msgstr "Annulla"
msgid "Save"
msgstr "Salva"
msgid "Port configuration saved successfully"
msgstr "Configurazione della porta salvata con successo"
msgid "Port order saved"
msgstr "Ordine delle porte salvato"
msgid "Click to edit label"
msgstr "Fai clic per modificare l'etichetta"
msgid "Hold to drag and reorder"
msgstr "Tieni premuto per trascinare e riordinare"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Etichetta max 9 caratteri, descrizione max 50 caratteri."
msgid "Enter description (optional):"
msgstr "Inserisci descrizione (facoltativo):"
msgid "Optional description"
msgstr "Descrizione facoltativa"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Le impostazioni utente vengono salvate nel file /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "File di configurazione scaricato"
msgid "Configuration file not found"
msgstr "File di configurazione non trovato"
msgid "Download error: %s"
msgstr "Errore di download: %s"
msgid "Invalid configuration format"
msgstr "Formato di configurazione non valido"
msgid "Restore configuration"
msgstr "Ripristina configurazione"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Questo sovrascriverà la configurazione attuale delle porte. Continuare?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Configurazione ripristinata con successo. Ricaricamento..."
msgid "File restore failed: %s"
msgstr "Ripristino del file non riuscito: %s"
msgid "Save .json file"
msgstr "Salva file .json"
msgid "Upload .json file"
msgstr "Carica file .json"
msgid "Cannot save empty configuration"
msgstr "Impossibile salvare una configurazione vuota"
msgid "Cannot save port configuration. "
msgstr "Impossibile salvare la configurazione delle porte. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "La directory /etc potrebbe essere in sola lettura o avere permessi insufficienti. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Prova a eseguire: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Errore di salvataggio"
msgid "Original error: %s"
msgstr "Errore originale: %s"
msgid "Restore from backup"
msgstr "Ripristina dal backup"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Ripristinare la configurazione dal file di backup? Questo sovrascriverà le impostazioni correnti."
msgid "Backup contains:"
msgstr "Il backup contiene:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Configurazione ripristinata dal backup. Ricaricamento..."
msgid "Restore failed: %s"
msgstr "Ripristino non riuscito: %s"
msgid "Backup file is corrupted: %s"
msgstr "Il file di backup è danneggiato: %s"
msgid "Error reading backup: %s"
msgstr "Errore durante la lettura del backup: %s"
msgid "Restore backup .bak"
msgstr "Ripristina backup .bak"
msgid "Change port state"
msgstr "Cambia stato porta"
msgid "Enable this port"
msgstr "Abilita questa porta"
msgid "Enable port"
msgstr "Abilita porta"
msgid "Disable this port"
msgstr "Disabilita questa porta"
msgid "Disable port"
msgstr "Disabilita porta"
msgid "Down"
msgstr "inattiva"
msgid "Disabled"
msgstr "disabilitata"
msgid "Idle"
msgstr "inattiva"
msgid "Active"
msgstr "attiva"
msgid "Unknown"
msgstr "sconosciuto"
msgid "No config file to backup"
msgstr "Nessun file di configurazione da salvare"
msgid "Config file is corrupted, cannot create backup"
msgstr "Il file di configurazione è corrotto, impossibile creare il backup"
msgid "Backup file created: %s"
msgstr "File di backup creato: %s"
msgid "Backup write verification failed!"
msgstr "Verifica della scrittura del backup non riuscita!"
msgid "Backup creation failed: %s"
msgstr "Creazione del backup non riuscita: %s"
msgid "Error reading config: %s"
msgstr "Errore durante la lettura della configurazione: %s"
msgid "Create backup .bak"
msgstr "Crea backup .bak"
@@ -1,174 +0,0 @@
msgid ""
msgstr ""
"Language: ja\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Edit Port Label"
msgstr "ポートラベルを編集"
msgid "Enter new label for this port:"
msgstr "このポートの新しいラベルを入力してください:"
msgid "Device"
msgstr "デバイス"
msgid "Original label"
msgstr "元のラベル"
msgid "Restore Original"
msgstr "元に戻す"
msgid "Cancel"
msgstr "キャンセル"
msgid "Save"
msgstr "保存"
msgid "Port configuration saved successfully"
msgstr "ポート設定が正常に保存されました"
msgid "Port order saved"
msgstr "ポート順序が保存されました"
msgid "Click to edit label"
msgstr "クリックしてラベルを編集"
msgid "Hold to drag and reorder"
msgstr "ドラッグして順序を変更するには長押し"
msgid "Label max 9 chars, description max 50 chars."
msgstr "ラベルは最大9文字、説明は最大50文字です。"
msgid "Enter description (optional):"
msgstr "説明を入力してください (オプション):"
msgid "Optional description"
msgstr "オプションの説明"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "ユーザー設定は /etc/user_defined_ports.json ファイルに保存されます。"
msgid "Configuration file downloaded"
msgstr "設定ファイルがダウンロードされました"
msgid "Configuration file not found"
msgstr "設定ファイルが見つかりません"
msgid "Download error: %s"
msgstr "ダウンロードエラー: %s"
msgid "Invalid configuration format"
msgstr "無効な設定形式"
msgid "Restore configuration"
msgstr "設定を復元"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "これにより現在のポート設定が上書きされます。続行しますか?"
msgid "Configuration restored successfully. Reloading..."
msgstr "設定が正常に復元されました。リロード中..."
msgid "File restore failed: %s"
msgstr "ファイルの復元に失敗しました: %s"
msgid "Save .json file"
msgstr ".json ファイルを保存"
msgid "Upload .json file"
msgstr ".json ファイルをアップロード"
msgid "Cannot save empty configuration"
msgstr "空の設定を保存できません"
msgid "Cannot save port configuration. "
msgstr "ポート設定を保存できません。"
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "ディレクトリ /etc は読み取り専用または権限不足の可能性があります。"
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "次のコマンドを実行してみてください: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "保存エラー"
msgid "Original error: %s"
msgstr "元のエラー: %s"
msgid "Restore from backup"
msgstr "バックアップから復元"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "バックアップファイルから設定を復元しますか? これにより現在の設定が上書きされます。"
msgid "Backup contains:"
msgstr "バックアップに含まれるもの:"
msgid "Configuration restored from backup. Reloading..."
msgstr "バックアップから設定が復元されました。リロード中..."
msgid "Restore failed: %s"
msgstr "復元に失敗しました: %s"
msgid "Backup file is corrupted: %s"
msgstr "バックアップファイルが破損しています: %s"
msgid "Error reading backup: %s"
msgstr "バックアップの読み込みエラー: %s"
msgid "Restore backup .bak"
msgstr "バックアップ .bak を復元"
msgid "Change port state"
msgstr "ポート状態を変更"
msgid "Enable this port"
msgstr "このポートを有効にする"
msgid "Enable port"
msgstr "ポートを有効化"
msgid "Disable this port"
msgstr "このポートを無効にする"
msgid "Disable port"
msgstr "ポートを無効化"
msgid "Down"
msgstr "ダウン"
msgid "Disabled"
msgstr "無効"
msgid "Idle"
msgstr "アイドル"
msgid "Active"
msgstr "アクティブ"
msgid "Unknown"
msgstr "不明"
msgid "No config file to backup"
msgstr "バックアップする設定ファイルがありません"
msgid "Config file is corrupted, cannot create backup"
msgstr "設定ファイルが破損しているため、バックアップを作成できません"
msgid "Backup file created: %s"
msgstr "バックアップファイルが作成されました: %s"
msgid "Backup write verification failed!"
msgstr "バックアップ書き込み検証に失敗しました!"
msgid "Backup creation failed: %s"
msgstr "バックアップの作成に失敗しました: %s"
msgid "Error reading config: %s"
msgstr "設定の読み込みエラー: %s"
msgid "Create backup .bak"
msgstr "バックアップ .bak を作成"
@@ -1,174 +0,0 @@
msgid ""
msgstr ""
"Language: ko\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Edit Port Label"
msgstr "포트 레이블 편집"
msgid "Enter new label for this port:"
msgstr "이 포트의 새 레이블을 입력하세요:"
msgid "Device"
msgstr "장치"
msgid "Original label"
msgstr "원본 레이블"
msgid "Restore Original"
msgstr "원본 복원"
msgid "Cancel"
msgstr "취소"
msgid "Save"
msgstr "저장"
msgid "Port configuration saved successfully"
msgstr "포트 설정이 성공적으로 저장되었습니다"
msgid "Port order saved"
msgstr "포트 순서가 저장되었습니다"
msgid "Click to edit label"
msgstr "레이블을 편집하려면 클릭"
msgid "Hold to drag and reorder"
msgstr "드래그하여 순서를 변경하려면 누르고 있기"
msgid "Label max 9 chars, description max 50 chars."
msgstr "레이블 최대 9자, 설명 최대 50자."
msgid "Enter description (optional):"
msgstr "설명을 입력하세요 (선택 사항):"
msgid "Optional description"
msgstr "선택 사항 설명"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "사용자 설정은 /etc/user_defined_ports.json 파일에 저장됩니다."
msgid "Configuration file downloaded"
msgstr "설정 파일이 다운로드되었습니다"
msgid "Configuration file not found"
msgstr "설정 파일을 찾을 수 없습니다"
msgid "Download error: %s"
msgstr "다운로드 오류: %s"
msgid "Invalid configuration format"
msgstr "잘못된 설정 형식"
msgid "Restore configuration"
msgstr "설정 복원"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "이 작업은 현재 포트 설정을 덮어씁니다. 계속하시겠습니까?"
msgid "Configuration restored successfully. Reloading..."
msgstr "설정이 성공적으로 복원되었습니다. 다시 로드 중..."
msgid "File restore failed: %s"
msgstr "파일 복원 실패: %s"
msgid "Save .json file"
msgstr ".json 파일 저장"
msgid "Upload .json file"
msgstr ".json 파일 업로드"
msgid "Cannot save empty configuration"
msgstr "빈 설정을 저장할 수 없습니다"
msgid "Cannot save port configuration. "
msgstr "포트 설정을 저장할 수 없습니다. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "/etc 디렉토리가 읽기 전용이거나 권한이 부족할 수 있습니다. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "다음 명령어를 실행해보세요: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "저장 오류"
msgid "Original error: %s"
msgstr "원본 오류: %s"
msgid "Restore from backup"
msgstr "백업에서 복원"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "백업 파일에서 설정을 복원하시겠습니까? 이 작업은 현재 설정을 덮어씁니다."
msgid "Backup contains:"
msgstr "백업에 포함됨:"
msgid "Configuration restored from backup. Reloading..."
msgstr "백업에서 설정이 복원되었습니다. 다시 로드 중..."
msgid "Restore failed: %s"
msgstr "복원 실패: %s"
msgid "Backup file is corrupted: %s"
msgstr "백업 파일이 손상됨: %s"
msgid "Error reading backup: %s"
msgstr "백업 읽기 오류: %s"
msgid "Restore backup .bak"
msgstr "백업 .bak 복원"
msgid "Change port state"
msgstr "포트 상태 변경"
msgid "Enable this port"
msgstr "이 포트 활성화"
msgid "Enable port"
msgstr "포트 활성화"
msgid "Disable this port"
msgstr "이 포트 비활성화"
msgid "Disable port"
msgstr "포트 비활성화"
msgid "Down"
msgstr "다운"
msgid "Disabled"
msgstr "비활성화됨"
msgid "Idle"
msgstr "유휴 상태"
msgid "Active"
msgstr "활성"
msgid "Unknown"
msgstr "알 수 없음"
msgid "No config file to backup"
msgstr "백업할 설정 파일이 없습니다"
msgid "Config file is corrupted, cannot create backup"
msgstr "설정 파일이 손상되어 백업을 만들 수 없습니다"
msgid "Backup file created: %s"
msgstr "백업 파일이 생성되었습니다: %s"
msgid "Backup write verification failed!"
msgstr "백업 쓰기 검증 실패!"
msgid "Backup creation failed: %s"
msgstr "백업 생성 실패: %s"
msgid "Error reading config: %s"
msgstr "설정 읽기 오류: %s"
msgid "Create backup .bak"
msgstr "백업 .bak 생성"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: nl\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
msgid "Edit Port Label"
msgstr "Poortlabel bewerken"
msgid "Enter new label for this port:"
msgstr "Voer nieuw label in voor deze poort:"
msgid "Device"
msgstr "Apparaat"
msgid "Original label"
msgstr "Oorspronkelijk label"
msgid "Restore Original"
msgstr "Oorspronkelijke herstellen"
msgid "Cancel"
msgstr "Annuleren"
msgid "Save"
msgstr "Opslaan"
msgid "Port configuration saved successfully"
msgstr "Poortconfiguratie succesvol opgeslagen"
msgid "Port order saved"
msgstr "Poortvolgorde opgeslagen"
msgid "Click to edit label"
msgstr "Klik om label te bewerken"
msgid "Hold to drag and reorder"
msgstr "Vasthouden om te slepen en de volgorde te wijzigen"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Label max. 9 tekens, beschrijving max. 50 tekens."
msgid "Enter description (optional):"
msgstr "Voer beschrijving in (optioneel):"
msgid "Optional description"
msgstr "Optionele beschrijving"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Gebruikersinstellingen worden opgeslagen in het bestand /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Configuratiebestand gedownload"
msgid "Configuration file not found"
msgstr "Configuratiebestand niet gevonden"
msgid "Download error: %s"
msgstr "Downloadfout: %s"
msgid "Invalid configuration format"
msgstr "Ongeldig configuratieformaat"
msgid "Restore configuration"
msgstr "Configuratie herstellen"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Dit zal de huidige poortconfiguratie overschrijven. Doorgaan?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Configuratie succesvol hersteld. Herladen..."
msgid "File restore failed: %s"
msgstr "Bestandsherstel mislukt: %s"
msgid "Save .json file"
msgstr "JSON-bestand opslaan"
msgid "Upload .json file"
msgstr "JSON-bestand uploaden"
msgid "Cannot save empty configuration"
msgstr "Kan geen lege configuratie opslaan"
msgid "Cannot save port configuration. "
msgstr "Kan poortconfiguratie niet opslaan. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "De map /etc is mogelijk alleen-lezen of heeft onvoldoende rechten. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Probeer het volgende uit te voeren: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Opslagfout"
msgid "Original error: %s"
msgstr "Oorspronkelijke fout: %s"
msgid "Restore from backup"
msgstr "Herstellen van back-up"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Configuratie herstellen van back-upbestand? Dit zal de huidige instellingen overschrijven."
msgid "Backup contains:"
msgstr "Back-up bevat:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Configuratie hersteld van back-up. Herladen..."
msgid "Restore failed: %s"
msgstr "Herstel mislukt: %s"
msgid "Backup file is corrupted: %s"
msgstr "Back-upbestand is beschadigd: %s"
msgid "Error reading backup: %s"
msgstr "Fout bij lezen van back-up: %s"
msgid "Restore backup .bak"
msgstr ".bak-back-up herstellen"
msgid "Change port state"
msgstr "Poortstatus wijzigen"
msgid "Enable this port"
msgstr "Deze poort inschakelen"
msgid "Enable port"
msgstr "Poort inschakelen"
msgid "Disable this port"
msgstr "Deze poort uitschakelen"
msgid "Disable port"
msgstr "Poort uitschakelen"
msgid "Down"
msgstr "down"
msgid "Disabled"
msgstr "uitgeschakeld"
msgid "Idle"
msgstr "onactief"
msgid "Active"
msgstr "actief"
msgid "Unknown"
msgstr "onbekend"
msgid "No config file to backup"
msgstr "Geen configuratiebestand om back-up van te maken"
msgid "Config file is corrupted, cannot create backup"
msgstr "Configuratiebestand is beschadigd, kan geen back-up maken"
msgid "Backup file created: %s"
msgstr "Back-upbestand aangemaakt: %s"
msgid "Backup write verification failed!"
msgstr "Schrijfverificatie van back-up mislukt!"
msgid "Backup creation failed: %s"
msgstr "Aanmaken van back-up mislukt: %s"
msgid "Error reading config: %s"
msgstr "Fout bij lezen van configuratie: %s"
msgid "Create backup .bak"
msgstr ".bak-back-up maken"
@@ -1,170 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Edit Port Label"
msgstr "Edytuj etykietę portu"
msgid "Enter new label for this port:"
msgstr "Wprowadź nową etykietę dla tego portu:"
msgid "Device"
msgstr "Urządzenie"
msgid "Original label"
msgstr "Oryginalna etykieta"
msgid "Restore Original"
msgstr "Przywróć oryginalną"
msgid "Cancel"
msgstr "Anuluj"
msgid "Save"
msgstr "Zapisz"
msgid "Port configuration saved successfully"
msgstr "Konfiguracja portu została poprawnie zapisana"
msgid "Port order saved"
msgstr "Zapisano kolejność portu"
msgid "Click to edit label"
msgstr "Kliknij aby edytować etykietę"
msgid "Hold to drag and reorder"
msgstr "Przytrzymaj aby przeciągnąć i zmienić kolejność"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Etykieta to maksymalnie 9 znaków, limit dla opisu wynosi 50 znaków."
msgid "Enter description (optional):"
msgstr "Dodaj opis (opcjonalnie):"
msgid "Optional description"
msgstr "Opcjonalny opis portu"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Ustawienia użytkownika zapisywane są w pliku /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Pobrano plik z ustawieniami"
msgid "Configuration file not found"
msgstr "Nie znaleziono pliku z ustawieniami"
msgid "Download error: %s"
msgstr "Błąd pobierania: %s"
msgid "Invalid configuration format"
msgstr "Niewłaściwy format pliku"
msgid "Restore configuration"
msgstr "Przywracanie ustawień"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Operacja nadpisze bieżącą konfigurację portów. Kontynuować?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Konfiguracja została pomyślnie przywrócona. Odświeżanie..."
msgid "File restore failed: %s"
msgstr "Przywracanie pliku nie powiodło się: %s"
msgid "Save .json file"
msgstr "Zapisz plik .json"
msgid "Upload .json file"
msgstr "Załaduj plik .json"
msgid "Cannot save empty configuration"
msgstr "Nie można zapisać pustej konfiguracji"
msgid "Cannot save port configuration. "
msgstr "Nie można zapisać konfiguracji portów. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Katalog /etc może być tylko do odczytu lub mieć niewystarczające uprawnienia. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Spróbuj uruchomić: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Błąd zapisu"
msgid "Original error: %s"
msgstr "Oryginalny błąd: %s"
msgid "Restore from backup"
msgstr "Przywróć z kopii zapasowej"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Przywrócić konfigurację z pliku kopii zapasowej? Spowoduje to nadpisanie bieżących ustawień."
msgid "Backup contains:"
msgstr "Kopia zapasowa zawiera:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Konfiguracja przywrócona z kopii zapasowej. Odświeżanie..."
msgid "Restore failed: %s"
msgstr "Przywracanie nie powiodło się: %s"
msgid "Backup file is corrupted: %s"
msgstr "Plik kopii zapasowej jest uszkodzony: %s"
msgid "Error reading backup: %s"
msgstr "Błąd odczytu kopii zapasowej: %s"
msgid "Restore backup .bak"
msgstr "Przywróć kopię .bak"
msgid "Change port state"
msgstr "Zmień stan portu"
msgid "Enable this port"
msgstr "Włącz ten port"
msgid "Enable port"
msgstr "Włącz port"
msgid "Disable this port"
msgstr "Wyłącz ten port"
msgid "Disable port"
msgstr "Wyłącz port"
msgid "Down"
msgstr "nieaktywny"
msgid "Disabled"
msgstr "wyłączony"
msgid "Idle"
msgstr "bezczynny"
msgid "Active"
msgstr "aktywny"
msgid "Unknown"
msgstr "nieznany"
msgid "No config file to backup"
msgstr "Brak pliku konfiguracji do utworzenia kopii zapasowej"
msgid "Config file is corrupted, cannot create backup"
msgstr "Plik konfiguracji jest uszkodzony, nie można utworzyć kopii zapasowej"
msgid "Backup file created: %s"
msgstr "Plik kopii zapasowej został utworzony: %s"
msgid "Backup write verification failed!"
msgstr "Weryfikacja zapisu kopii zapasowej nie powiodła się!"
msgid "Backup creation failed: %s"
msgstr "Tworzenie kopii zapasowej nie powiodło się: %s"
msgid "Error reading config: %s"
msgstr "Błąd odczytu konfiguracji: %s"
msgid "Create backup .bak"
msgstr "Utwórz kopię .bak"
@@ -1,174 +0,0 @@
msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
msgid "Edit Port Label"
msgstr "Editar rótulo da porta"
msgid "Enter new label for this port:"
msgstr "Digite um novo rótulo para esta porta:"
msgid "Device"
msgstr "Dispositivo"
msgid "Original label"
msgstr "Rótulo original"
msgid "Restore Original"
msgstr "Restaurar original"
msgid "Cancel"
msgstr "Cancelar"
msgid "Save"
msgstr "Salvar"
msgid "Port configuration saved successfully"
msgstr "Configuração da porta salva com sucesso"
msgid "Port order saved"
msgstr "Ordem das portas salva"
msgid "Click to edit label"
msgstr "Clique para editar rótulo"
msgid "Hold to drag and reorder"
msgstr "Segure para arrastar e reordenar"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Rótulo máximo de 9 caracteres, descrição máximo de 50 caracteres."
msgid "Enter description (optional):"
msgstr "Digite descrição (opcional):"
msgid "Optional description"
msgstr "Descrição opcional"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "As configurações do usuário são salvas no arquivo /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Arquivo de configuração baixado"
msgid "Configuration file not found"
msgstr "Arquivo de configuração não encontrado"
msgid "Download error: %s"
msgstr "Erro de download: %s"
msgid "Invalid configuration format"
msgstr "Formato de configuração inválido"
msgid "Restore configuration"
msgstr "Restaurar configuração"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Isso sobrescreverá a configuração atual das portas. Continuar?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Configuração restaurada com sucesso. Recarregando..."
msgid "File restore failed: %s"
msgstr "Falha na restauração do arquivo: %s"
msgid "Save .json file"
msgstr "Salvar arquivo .json"
msgid "Upload .json file"
msgstr "Carregar arquivo .json"
msgid "Cannot save empty configuration"
msgstr "Não é possível salvar uma configuração vazia"
msgid "Cannot save port configuration. "
msgstr "Não é possível salvar a configuração da porta. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "O diretório /etc pode estar somente leitura ou ter permissões insuficientes. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Tente executar: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Erro ao salvar"
msgid "Original error: %s"
msgstr "Erro original: %s"
msgid "Restore from backup"
msgstr "Restaurar do backup"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Restaurar configuração do arquivo de backup? Isso sobrescreverá as configurações atuais."
msgid "Backup contains:"
msgstr "Backup contém:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Configuração restaurada do backup. Recarregando..."
msgid "Restore failed: %s"
msgstr "Falha na restauração: %s"
msgid "Backup file is corrupted: %s"
msgstr "Arquivo de backup corrompido: %s"
msgid "Error reading backup: %s"
msgstr "Erro ao ler backup: %s"
msgid "Restore backup .bak"
msgstr "Restaurar backup .bak"
msgid "Change port state"
msgstr "Alterar estado da porta"
msgid "Enable this port"
msgstr "Ativar esta porta"
msgid "Enable port"
msgstr "Ativar porta"
msgid "Disable this port"
msgstr "Desativar esta porta"
msgid "Disable port"
msgstr "Desativar porta"
msgid "Down"
msgstr "Inativo"
msgid "Disabled"
msgstr "Desativado"
msgid "Idle"
msgstr "Ocioso"
msgid "Active"
msgstr "Ativo"
msgid "Unknown"
msgstr "Desconhecido"
msgid "No config file to backup"
msgstr "Nenhum arquivo de configuração para fazer backup"
msgid "Config file is corrupted, cannot create backup"
msgstr "Arquivo de configuração corrompido, não é possível criar backup"
msgid "Backup file created: %s"
msgstr "Arquivo de backup criado: %s"
msgid "Backup write verification failed!"
msgstr "Falha na verificação de escrita do backup!"
msgid "Backup creation failed: %s"
msgstr "Falha na criação do backup: %s"
msgid "Error reading config: %s"
msgstr "Erro ao ler configuração: %s"
msgid "Create backup .bak"
msgstr "Criar backup .bak"
@@ -1,176 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: ru\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "Edit Port Label"
msgstr "Редактировать метку порта"
msgid "Enter new label for this port:"
msgstr "Введите новую метку для этого порта:"
msgid "Device"
msgstr "Устройство"
msgid "Original label"
msgstr "Исходная метка"
msgid "Restore Original"
msgstr "Восстановить исходную"
msgid "Cancel"
msgstr "Отмена"
msgid "Save"
msgstr "Сохранить"
msgid "Port configuration saved successfully"
msgstr "Конфигурация порта успешно сохранена"
msgid "Port order saved"
msgstr "Порядок портов сохранен"
msgid "Click to edit label"
msgstr "Нажмите, чтобы изменить метку"
msgid "Hold to drag and reorder"
msgstr "Удерживайте, чтобы перетащить и изменить порядок"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Метка макс. 9 символов, описание макс. 50 символов."
msgid "Enter description (optional):"
msgstr "Введите описание (необязательно):"
msgid "Optional description"
msgstr "Необязательное описание"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Пользовательские настройки сохраняются в файле /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Файл конфигурации загружен"
msgid "Configuration file not found"
msgstr "Файл конфигурации не найден"
msgid "Download error: %s"
msgstr "Ошибка загрузки: %s"
msgid "Invalid configuration format"
msgstr "Неверный формат конфигурации"
msgid "Restore configuration"
msgstr "Восстановление конфигурации"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Это перезапишет текущую конфигурацию портов. Продолжить?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Конфигурация успешно восстановлена. Перезагрузка..."
msgid "File restore failed: %s"
msgstr "Не удалось восстановить файл: %s"
msgid "Save .json file"
msgstr "Сохранить файл .json"
msgid "Upload .json file"
msgstr "Загрузить файл .json"
msgid "Cannot save empty configuration"
msgstr "Невозможно сохранить пустую конфигурацию"
msgid "Cannot save port configuration. "
msgstr "Невозможно сохранить конфигурацию портов. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Каталог /etc может быть доступен только для чтения или иметь недостаточные права доступа. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Попробуйте выполнить: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Ошибка сохранения"
msgid "Original error: %s"
msgstr "Исходная ошибка: %s"
msgid "Restore from backup"
msgstr "Восстановить из резервной копии"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Восстановить конфигурацию из файла резервной копии? Это перезапишет текущие настройки."
msgid "Backup contains:"
msgstr "Резервная копия содержит:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Конфигурация восстановлена из резервной копии. Перезагрузка..."
msgid "Restore failed: %s"
msgstr "Восстановление не удалось: %s"
msgid "Backup file is corrupted: %s"
msgstr "Файл резервной копии поврежден: %s"
msgid "Error reading backup: %s"
msgstr "Ошибка чтения резервной копии: %s"
msgid "Restore backup .bak"
msgstr "Восстановить резервную копию .bak"
msgid "Change port state"
msgstr "Изменить состояние порта"
msgid "Enable this port"
msgstr "Включить этот порт"
msgid "Enable port"
msgstr "Включить порт"
msgid "Disable this port"
msgstr "Отключить этот порт"
msgid "Disable port"
msgstr "Отключить порт"
msgid "Down"
msgstr "неактивен"
msgid "Disabled"
msgstr "отключен"
msgid "Idle"
msgstr "простаивает"
msgid "Active"
msgstr "активен"
msgid "Unknown"
msgstr "неизвестно"
msgid "No config file to backup"
msgstr "Нет файла конфигурации для резервного копирования"
msgid "Config file is corrupted, cannot create backup"
msgstr "Файл конфигурации повреждён, создание резервной копии невозможно"
msgid "Backup file created: %s"
msgstr "Резервный файл создан: %s"
msgid "Backup write verification failed!"
msgstr "Проверка записи резервной копии не удалась!"
msgid "Backup creation failed: %s"
msgstr "Создание резервной копии не удалось: %s"
msgid "Error reading config: %s"
msgstr "Ошибка чтения конфигурации: %s"
msgid "Create backup .bak"
msgstr "Создать резервную копию .bak"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: vi\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Edit Port Label"
msgstr "Chỉnh sửa nhãn cổng"
msgid "Enter new label for this port:"
msgstr "Nhập nhãn mới cho cổng này:"
msgid "Device"
msgstr "Thiết bị"
msgid "Original label"
msgstr "Nhãn gốc"
msgid "Restore Original"
msgstr "Khôi phục gốc"
msgid "Cancel"
msgstr "Hủy"
msgid "Save"
msgstr "Lưu"
msgid "Port configuration saved successfully"
msgstr "Đã lưu cấu hình cổng thành công"
msgid "Port order saved"
msgstr "Đã lưu thứ tự cổng"
msgid "Click to edit label"
msgstr "Nhấp để chỉnh sửa nhãn"
msgid "Hold to drag and reorder"
msgstr "Giữ để kéo và sắp xếp lại"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Nhãn tối đa 9 ký tự, mô tả tối đa 50 ký tự."
msgid "Enter description (optional):"
msgstr "Nhập mô tả (tùy chọn):"
msgid "Optional description"
msgstr "Mô tả tùy chọn"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Cài đặt người dùng được lưu vào tệp /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Đã tải xuống tệp cấu hình"
msgid "Configuration file not found"
msgstr "Không tìm thấy tệp cấu hình"
msgid "Download error: %s"
msgstr "Lỗi tải xuống: %s"
msgid "Invalid configuration format"
msgstr "Định dạng cấu hình không hợp lệ"
msgid "Restore configuration"
msgstr "Khôi phục cấu hình"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Thao tác này sẽ ghi đè cấu hình cổng hiện tại. Tiếp tục?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Đã khôi phục cấu hình thành công. Đang tải lại..."
msgid "File restore failed: %s"
msgstr "Khôi phục tệp không thành công: %s"
msgid "Save .json file"
msgstr "Lưu tệp .json"
msgid "Upload .json file"
msgstr "Tải lên tệp .json"
msgid "Cannot save empty configuration"
msgstr "Không thể lưu cấu hình trống"
msgid "Cannot save port configuration. "
msgstr "Không thể lưu cấu hình cổng. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Thư mục /etc có thể chỉ đọc hoặc không đủ quyền. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Thử chạy: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Lỗi lưu"
msgid "Original error: %s"
msgstr "Lỗi gốc: %s"
msgid "Restore from backup"
msgstr "Khôi phục từ bản sao lưu"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Khôi phục cấu hình từ tệp sao lưu? Thao tác này sẽ ghi đè cài đặt hiện tại."
msgid "Backup contains:"
msgstr "Bản sao lưu chứa:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Đã khôi phục cấu hình từ bản sao lưu. Đang tải lại..."
msgid "Restore failed: %s"
msgstr "Khôi phục không thành công: %s"
msgid "Backup file is corrupted: %s"
msgstr "Tệp sao lưu bị hỏng: %s"
msgid "Error reading backup: %s"
msgstr "Lỗi đọc bản sao lưu: %s"
msgid "Restore backup .bak"
msgstr "Khôi phục bản sao lưu .bak"
msgid "Change port state"
msgstr "Thay đổi trạng thái cổng"
msgid "Enable this port"
msgstr "Bật cổng này"
msgid "Enable port"
msgstr "Bật cổng"
msgid "Disable this port"
msgstr "Tắt cổng này"
msgid "Disable port"
msgstr "Tắt cổng"
msgid "Down"
msgstr "không hoạt động"
msgid "Disabled"
msgstr "đã tắt"
msgid "Idle"
msgstr "rảnh rỗi"
msgid "Active"
msgstr "hoạt động"
msgid "Unknown"
msgstr "không rõ"
msgid "No config file to backup"
msgstr "Không có tệp cấu hình để sao lưu"
msgid "Config file is corrupted, cannot create backup"
msgstr "Tệp cấu hình bị hỏng, không thể tạo bản sao lưu"
msgid "Backup file created: %s"
msgstr "Đã tạo tệp sao lưu: %s"
msgid "Backup write verification failed!"
msgstr "Xác minh ghi bản sao lưu thất bại!"
msgid "Backup creation failed: %s"
msgstr "Tạo bản sao lưu thất bại: %s"
msgid "Error reading config: %s"
msgstr "Lỗi khi đọc cấu hình: %s"
msgid "Create backup .bak"
msgstr "Tạo bản sao lưu .bak"
@@ -1,122 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Edit Port Label"
msgstr "Chỉnh sửa nhãn cổng"
msgid "Enter new label for this port:"
msgstr "Nhập nhãn mới cho cổng này:"
msgid "Device"
msgstr "Thiết bị"
msgid "Original label"
msgstr "Nhãn gốc"
msgid "Restore Original"
msgstr "Khôi phục gốc"
msgid "Cancel"
msgstr "Hủy"
msgid "Save"
msgstr "Lưu"
msgid "Port configuration saved successfully"
msgstr "Cấu hình cổng đã được lưu thành công"
msgid "Port order saved"
msgstr "Thứ tự cổng đã được lưu"
msgid "Click to edit label"
msgstr "Nhấp để chỉnh sửa nhãn"
msgid "Hold to drag and reorder"
msgstr "Giữ để kéo và sắp xếp lại"
msgid "Label max 9 chars, description max 50 chars."
msgstr "Nhãn tối đa 9 ký tự, mô tả tối đa 50 ký tự."
msgid "Enter description (optional):"
msgstr "Nhập mô tả (tùy chọn):"
msgid "Optional description"
msgstr "Mô tả tùy chọn"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "Cài đặt người dùng được lưu vào tệp /etc/user_defined_ports.json."
msgid "Configuration file downloaded"
msgstr "Đã tải xuống tệp cấu hình"
msgid "Configuration file not found"
msgstr "Không tìm thấy tệp cấu hình"
msgid "Download error: %s"
msgstr "Lỗi tải xuống: %s"
msgid "Invalid configuration format"
msgstr "Định dạng cấu hình không hợp lệ"
msgid "Restore configuration"
msgstr "Khôi phục cấu hình"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "Thao tác này sẽ ghi đè cấu hình cổng hiện tại. Tiếp tục?"
msgid "Configuration restored successfully. Reloading..."
msgstr "Cấu hình đã được khôi phục thành công. Đang tải lại..."
msgid "File restore failed: %s"
msgstr "Khôi phục tệp không thành công: %s"
msgid "Save .json file"
msgstr "Lưu tệp .json"
msgid "Upload .json file"
msgstr "Tải lên tệp .json"
msgid "Cannot save empty configuration"
msgstr "Không thể lưu cấu hình trống"
msgid "Cannot save port configuration. "
msgstr "Không thể lưu cấu hình cổng. "
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "Thư mục /etc có thể chỉ đọc hoặc không đủ quyền. "
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "Thử chạy: chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "Lỗi lưu"
msgid "Original error: %s"
msgstr "Lỗi gốc: %s"
msgid "Restore from backup"
msgstr "Khôi phục từ bản sao lưu"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "Khôi phục cấu hình từ tệp sao lưu? Thao tác này sẽ ghi đè các cài đặt hiện tại."
msgid "Backup contains:"
msgstr "Bản sao lưu chứa:"
msgid "Configuration restored from backup. Reloading..."
msgstr "Cấu hình đã được khôi phục từ bản sao lưu. Đang tải lại..."
msgid "Restore failed: %s"
msgstr "Khôi phục không thành công: %s"
msgid "Backup file is corrupted: %s"
msgstr "Tệp sao lưu bị hỏng: %s"
msgid "Error reading backup: %s"
msgstr "Lỗi đọc bản sao lưu: %s"
msgid "Restore backup .bak"
msgstr "Khôi phục bản sao lưu .bak"
msgid ""
msgstr ""
-1
View File
@@ -1 +0,0 @@
zh_Hans
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: zh_Hans\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Edit Port Label"
msgstr "编辑端口标签"
msgid "Enter new label for this port:"
msgstr "输入此端口的新标签:"
msgid "Device"
msgstr "设备"
msgid "Original label"
msgstr "原始标签"
msgid "Restore Original"
msgstr "恢复原始"
msgid "Cancel"
msgstr "取消"
msgid "Save"
msgstr "保存"
msgid "Port configuration saved successfully"
msgstr "端口配置已成功保存"
msgid "Port order saved"
msgstr "端口顺序已保存"
msgid "Click to edit label"
msgstr "点击编辑标签"
msgid "Hold to drag and reorder"
msgstr "按住以拖动和重新排序"
msgid "Label max 9 chars, description max 50 chars."
msgstr "标签最多 9 个字符,描述最多 50 个字符。"
msgid "Enter description (optional):"
msgstr "输入描述(可选):"
msgid "Optional description"
msgstr "可选描述"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "用户设置保存在 /etc/user_defined_ports.json 文件中。"
msgid "Configuration file downloaded"
msgstr "配置文件已下载"
msgid "Configuration file not found"
msgstr "未找到配置文件"
msgid "Download error: %s"
msgstr "下载错误:%s"
msgid "Invalid configuration format"
msgstr "无效的配置格式"
msgid "Restore configuration"
msgstr "恢复配置"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "这将覆盖当前端口配置。继续?"
msgid "Configuration restored successfully. Reloading..."
msgstr "配置已成功恢复。正在重新加载..."
msgid "File restore failed: %s"
msgstr "文件恢复失败:%s"
msgid "Save .json file"
msgstr "保存 .json 文件"
msgid "Upload .json file"
msgstr "上传 .json 文件"
msgid "Cannot save empty configuration"
msgstr "无法保存空配置"
msgid "Cannot save port configuration. "
msgstr "无法保存端口配置。"
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "目录 /etc 可能为只读或权限不足。"
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "尝试运行:chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "保存错误"
msgid "Original error: %s"
msgstr "原始错误:%s"
msgid "Restore from backup"
msgstr "从备份恢复"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "从备份文件恢复配置?这将覆盖当前设置。"
msgid "Backup contains:"
msgstr "备份包含:"
msgid "Configuration restored from backup. Reloading..."
msgstr "已从备份恢复配置。正在重新加载..."
msgid "Restore failed: %s"
msgstr "恢复失败:%s"
msgid "Backup file is corrupted: %s"
msgstr "备份文件已损坏:%s"
msgid "Error reading backup: %s"
msgstr "读取备份错误:%s"
msgid "Restore backup .bak"
msgstr "恢复备份 .bak"
msgid "Change port state"
msgstr "更改端口状态"
msgid "Enable this port"
msgstr "启用此端口"
msgid "Enable port"
msgstr "启用端口"
msgid "Disable this port"
msgstr "禁用此端口"
msgid "Disable port"
msgstr "禁用端口"
msgid "Down"
msgstr "未连接"
msgid "Disabled"
msgstr "已禁用"
msgid "Idle"
msgstr "空闲"
msgid "Active"
msgstr "活动"
msgid "Unknown"
msgstr "未知"
msgid "No config file to backup"
msgstr "没有可备份的配置文件"
msgid "Config file is corrupted, cannot create backup"
msgstr "配置文件已损坏,无法创建备份"
msgid "Backup file created: %s"
msgstr "备份文件已创建:%s"
msgid "Backup write verification failed!"
msgstr "备份写入验证失败!"
msgid "Backup creation failed: %s"
msgstr "备份创建失败:%s"
msgid "Error reading config: %s"
msgstr "读取配置时出错:%s"
msgid "Create backup .bak"
msgstr "创建备份 .bak"
@@ -1,175 +0,0 @@
msgid ""
msgstr ""
"Language-Team: Rafał Wabik (IceG)\n"
"Language: zh_Hant\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Edit Port Label"
msgstr "編輯連接埠標籤"
msgid "Enter new label for this port:"
msgstr "輸入此連接埠的新標籤:"
msgid "Device"
msgstr "裝置"
msgid "Original label"
msgstr "原始標籤"
msgid "Restore Original"
msgstr "恢復原始"
msgid "Cancel"
msgstr "取消"
msgid "Save"
msgstr "儲存"
msgid "Port configuration saved successfully"
msgstr "連接埠配置已成功儲存"
msgid "Port order saved"
msgstr "連接埠順序已儲存"
msgid "Click to edit label"
msgstr "點擊編輯標籤"
msgid "Hold to drag and reorder"
msgstr "按住以拖曳和重新排序"
msgid "Label max 9 chars, description max 50 chars."
msgstr "標籤最多 9 個字元,描述最多 50 個字元。"
msgid "Enter description (optional):"
msgstr "輸入描述(選填):"
msgid "Optional description"
msgstr "選填描述"
msgid "User settings are saved to the /etc/user_defined_ports.json file."
msgstr "使用者設定儲存在 /etc/user_defined_ports.json 檔案中。"
msgid "Configuration file downloaded"
msgstr "配置檔案已下載"
msgid "Configuration file not found"
msgstr "未找到配置檔案"
msgid "Download error: %s"
msgstr "下載錯誤:%s"
msgid "Invalid configuration format"
msgstr "無效的配置格式"
msgid "Restore configuration"
msgstr "恢復配置"
msgid "This will overwrite current ports configuration. Continue?"
msgstr "這將覆蓋目前連接埠配置。繼續?"
msgid "Configuration restored successfully. Reloading..."
msgstr "配置已成功恢復。正在重新載入..."
msgid "File restore failed: %s"
msgstr "檔案恢復失敗:%s"
msgid "Save .json file"
msgstr "儲存 .json 檔案"
msgid "Upload .json file"
msgstr "上傳 .json 檔案"
msgid "Cannot save empty configuration"
msgstr "無法儲存空白配置"
msgid "Cannot save port configuration. "
msgstr "無法儲存連接埠配置。"
msgid "Directory /etc may be read-only or insufficient permissions. "
msgstr "目錄 /etc 可能為唯讀或權限不足。"
msgid "Try running: chmod 755 /etc && touch %s && chmod 644 %s"
msgstr "嘗試執行:chmod 755 /etc && touch %s && chmod 644 %s"
msgid "Save Error"
msgstr "儲存錯誤"
msgid "Original error: %s"
msgstr "原始錯誤:%s"
msgid "Restore from backup"
msgstr "從備份恢復"
msgid "Restore configuration from backup file? This will overwrite current settings."
msgstr "從備份檔案恢復配置?這將覆蓋目前設定。"
msgid "Backup contains:"
msgstr "備份包含:"
msgid "Configuration restored from backup. Reloading..."
msgstr "已從備份恢復配置。正在重新載入..."
msgid "Restore failed: %s"
msgstr "恢復失敗:%s"
msgid "Backup file is corrupted: %s"
msgstr "備份檔案已損壞:%s"
msgid "Error reading backup: %s"
msgstr "讀取備份錯誤:%s"
msgid "Restore backup .bak"
msgstr "恢復備份 .bak"
msgid "Change port state"
msgstr "變更連接埠狀態"
msgid "Enable this port"
msgstr "啟用此連接埠"
msgid "Enable port"
msgstr "啟用連接埠"
msgid "Disable this port"
msgstr "停用此連接埠"
msgid "Disable port"
msgstr "停用連接埠"
msgid "Down"
msgstr "未連接"
msgid "Disabled"
msgstr "已停用"
msgid "Idle"
msgstr "閒置"
msgid "Active"
msgstr "活動"
msgid "Unknown"
msgstr "未知"
msgid "No config file to backup"
msgstr "沒有可備份的設定檔"
msgid "Config file is corrupted, cannot create backup"
msgstr "設定檔已損毀,無法建立備份"
msgid "Backup file created: %s"
msgstr "備份檔案已建立:%s"
msgid "Backup write verification failed!"
msgstr "備份寫入驗證失敗!"
msgid "Backup creation failed: %s"
msgstr "備份建立失敗:%s"
msgid "Error reading config: %s"
msgstr "讀取設定時發生錯誤:%s"
msgid "Create backup .bak"
msgstr "建立備份 .bak"
@@ -1,33 +0,0 @@
#!/bin/sh
#
# Copyright 2025-2026 Rafał Wabik (IceG) - From eko.one.pl forum
#
# MIT
#
PORTS_ORIG="/www/luci-static/resources/view/status/include/29_ports.js"
PORTS_BAK="/www/luci-static/resources/view/status/include/29_ports.bak"
PORTS_CUSTOM="/www/luci-static/resources/view/status/include/29_ports_custom.js"
USER_DEFINED_PORTS="/etc/user_defined_ports.json"
sleep 5
if [ -f "$PORTS_ORIG" ]; then
mv "$PORTS_ORIG" "$PORTS_BAK"
fi
if [ -f "$PORTS_CUSTOM" ]; then
mv "$PORTS_CUSTOM" "$PORTS_ORIG"
fi
if [ -f "$USER_DEFINED_PORTS" ]; then
chmod 664 "$USER_DEFINED_PORTS" >/dev/null 2>&1 &
fi
chmod +x /usr/libexec/rpcd/ports-status-mod >/dev/null 2>&1 &
rm -rf /tmp/luci-indexcache >/dev/null 2>&1 &
rm -rf /tmp/luci-* >/dev/null 2>&1 &
rm -rf /tmp/luci-modulecache/ >/dev/null 2>&1 &
exit 0
@@ -1,203 +0,0 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
##################################################################################################################
#
# Copyright 2026 Rafał Wabik - IceG - From eko.one.pl forum
#
# MIT License
#
##################################################################################################################
ACTIVITY_THRESHOLD=2000
check_activity() {
local port="$1"
local port_path="/sys/class/net/$port"
local rx_bytes_file="$port_path/statistics/rx_bytes"
local tx_bytes_file="$port_path/statistics/tx_bytes"
if [ ! -f "$rx_bytes_file" ] || [ ! -f "$tx_bytes_file" ]; then
echo "idle"
return
fi
local rx1 tx1 rx2 tx2
rx1=$(cat "$rx_bytes_file" 2>/dev/null || echo "0")
tx1=$(cat "$tx_bytes_file" 2>/dev/null || echo "0")
sleep 0.5
rx2=$(cat "$rx_bytes_file" 2>/dev/null || echo "0")
tx2=$(cat "$tx_bytes_file" 2>/dev/null || echo "0")
local rx_diff=$((rx2 - rx1))
local tx_diff=$((tx2 - tx1))
local total_diff=$((rx_diff + tx_diff))
if [ "$total_diff" -gt "$ACTIVITY_THRESHOLD" ]; then
echo "active"
else
echo "idle"
fi
}
get_port_status() {
local port="$1"
local port_path="/sys/class/net/$port"
local operstate="unknown"
if [ ! -d "$port_path" ]; then
echo "unknown"
return
fi
if [ -f "$port_path/operstate" ]; then
operstate=$(cat "$port_path/operstate" 2>/dev/null || echo "unknown")
fi
local flags=""
if [ -f "$port_path/flags" ]; then
flags=$(cat "$port_path/flags" 2>/dev/null || echo "0x0")
fi
local carrier=0
if [ -f "$port_path/carrier" ]; then
carrier=$(cat "$port_path/carrier" 2>/dev/null || echo "0")
fi
if [ "$operstate" = "down" ]; then
local flags_decimal=$(printf "%d" "$flags" 2>/dev/null || echo "0")
local is_up=$((flags_decimal & 1))
if [ "$is_up" = "0" ]; then
echo "disabled"
return
fi
fi
if [ "$carrier" != "1" ]; then
echo "down"
return
fi
if [ "$operstate" = "unknown" ] && [ "$carrier" = "1" ]; then
operstate="up"
fi
if [ "$carrier" = "1" ]; then
local activity=$(check_activity "$port")
echo "$activity"
return
fi
echo "$operstate"
}
get_all_ports_status() {
local tmpdir
tmpdir=$(mktemp -d)
local pids=""
for port_path in /sys/class/net/*; do
if [ -d "$port_path" ]; then
port_name=$(basename "$port_path")
case "$port_name" in
lo|dummy*|tunl*|gre*|gretap*|sit*|ip6tnl*|ip6gre*|br-*)
continue
;;
eth*|lan*|wan*|en*|sfp*)
if [ -f "$port_path/address" ]; then
(
status=$(get_port_status "$port_name")
echo "${port_name}=${status}" > "$tmpdir/$port_name"
) &
pids="$pids $!"
fi
;;
esac
fi
done
for pid in $pids; do
wait "$pid" 2>/dev/null
done
json_init
for result_file in "$tmpdir"/*; do
[ -f "$result_file" ] || continue
line=$(cat "$result_file")
port_name="${line%%=*}"
status="${line#*=}"
json_add_string "$port_name" "$status"
done
rm -rf "$tmpdir"
json_dump
}
set_port_status() {
local port="$1"
local status="$2"
if [ ! -d "/sys/class/net/$port" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Port not found"
json_dump
return 1
fi
if [ "$status" = "up" ]; then
ip link set "$port" up 2>/dev/null
elif [ "$status" = "down" ]; then
ip link set "$port" down 2>/dev/null
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Invalid status"
json_dump
return 1
fi
if [ $? -eq 0 ]; then
json_init
json_add_boolean "success" 1
json_dump
return 0
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to change port status"
json_dump
return 1
fi
}
case "$1" in
list)
json_init
json_add_object "getPortsStatus"
json_close_object
json_add_object "setPortStatus"
json_add_string "port" "port"
json_add_string "status" "status"
json_close_object
json_dump
;;
call)
case "$2" in
getPortsStatus)
get_all_ports_status
;;
setPortStatus)
read input
port=$(echo "$input" | jsonfilter -e '@.port')
status=$(echo "$input" | jsonfilter -e '@.status')
set_port_status "$port" "$status"
;;
esac
;;
esac
@@ -1,34 +0,0 @@
{
"luci-ports": {
"description": "Access to custom Ports Status",
"read": {
"uci": [ "*" ],
"ubus": {
"luci": [ "getBuiltinEthernetPorts" ],
"ports-status-mod": [ "getPortsStatus" ],
"file": [ "read" ]
},
"file": {
"/etc/user_defined_ports.json": [ "read" ],
"/etc/user_defined_ports.json.bak": [ "read" ],
"/www/luci-static/resources/view/status/include/29_ports.js": [ "read" ]
}
},
"write": {
"file": {
"/etc/user_defined_ports.json": [ "write" ],
"/etc/user_defined_ports.json.bak": [ "write" ]
},
"ubus": {
"file": [ "write", "exec" ],
"luci": [ "getBuiltinEthernetPorts" ],
"ports-status-mod": [ "setPortStatus" ]
},
"exec": {
"/usr/bin/stat": [ "*" ],
"/bin/chmod": [ "*" ],
"/bin/sh": [ "*" ]
}
}
}
}