From 82323be37ba8220cc4ad81d532d9d278272fd222 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Nov 2026 03:33:44 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=88=20Sync=202026-11-13=2003:33:44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-fancontrol/Makefile | 53 +- .../luci-static/resources/view/fancontrol.css | 171 ++ .../luci-static/resources/view/fancontrol.js | 463 +++++ .../resources/view/fancontrol/log.js | 88 - .../resources/view/fancontrol/overview.js | 329 ---- .../resources/view/fancontrol/settings.js | 76 - .../po/templates/fancontrol.pot | 261 --- luci-app-fancontrol/po/zh_Hans/fancontrol.po | 524 +++--- .../root/etc/config/fancontrol | 10 - .../root/etc/init.d/fancontrol | 44 - luci-app-fancontrol/root/usr/sbin/fancontrold | 249 --- .../luci/menu.d/luci-app-fancontrol.json | 36 +- .../share/rpcd/acl.d/luci-app-fancontrol.json | 18 +- .../root/usr/share/rpcd/ucode/luci.fancontrol | 223 --- luci-app-ports-status-mod/Makefile | 25 - .../view/status/include/29_ports_custom.js | 1566 ----------------- .../po/cs/Ports_Status.po | 175 -- .../po/da/Ports_Status.po | 175 -- .../po/de/Ports_Status.po | 175 -- .../po/es/Ports_Status.po | 175 -- .../po/fr/Ports_Status.po | 175 -- .../po/id/Ports_Status.po | 122 -- .../po/it/Ports_Status.po | 176 -- .../po/ja/Ports_Status.po | 174 -- .../po/ko/Ports_Status.po | 174 -- .../po/nl/Ports_Status.po | 175 -- .../po/pl/Ports_Status.po | 170 -- .../po/pt_BR/Ports_Status.po | 174 -- .../po/ru/Ports_Status.po | 176 -- .../po/vi/Ports_Status.po | 175 -- .../po/vn/Ports_Status.po | 122 -- luci-app-ports-status-mod/po/zh-cn | 1 - .../po/zh_Hans/Ports_Status.po | 175 -- .../po/zh_Hant/Ports_Status.po | 175 -- .../etc/uci-defaults/ports_status_magic.sh | 33 - .../root/usr/libexec/rpcd/ports-status-mod | 203 --- .../rpcd/acl.d/luci-app-ports-status-mod.json | 34 - 37 files changed, 938 insertions(+), 6332 deletions(-) create mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css create mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js delete mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js delete mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js delete mode 100644 luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js delete mode 100644 luci-app-fancontrol/po/templates/fancontrol.pot delete mode 100644 luci-app-fancontrol/root/etc/config/fancontrol delete mode 100755 luci-app-fancontrol/root/etc/init.d/fancontrol delete mode 100755 luci-app-fancontrol/root/usr/sbin/fancontrold delete mode 100644 luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol delete mode 100644 luci-app-ports-status-mod/Makefile delete mode 100644 luci-app-ports-status-mod/htdocs/luci-static/resources/view/status/include/29_ports_custom.js delete mode 100644 luci-app-ports-status-mod/po/cs/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/da/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/de/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/es/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/fr/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/id/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/it/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/ja/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/ko/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/nl/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/pl/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/pt_BR/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/ru/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/vi/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/vn/Ports_Status.po delete mode 120000 luci-app-ports-status-mod/po/zh-cn delete mode 100644 luci-app-ports-status-mod/po/zh_Hans/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/po/zh_Hant/Ports_Status.po delete mode 100644 luci-app-ports-status-mod/root/etc/uci-defaults/ports_status_magic.sh delete mode 100755 luci-app-ports-status-mod/root/usr/libexec/rpcd/ports-status-mod delete mode 100644 luci-app-ports-status-mod/root/usr/share/rpcd/acl.d/luci-app-ports-status-mod.json diff --git a/luci-app-fancontrol/Makefile b/luci-app-fancontrol/Makefile index ba484f4b0..ae3a4f59d 100644 --- a/luci-app-fancontrol/Makefile +++ b/luci-app-fancontrol/Makefile @@ -1,48 +1,21 @@ +# +# Copyright (C) 2008-2014 The LuCI Team +# +# 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 - -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 -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 diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css new file mode 100644 index 000000000..fa77bb072 --- /dev/null +++ b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.css @@ -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; + } +} diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js new file mode 100644 index 000000000..c270c4cdd --- /dev/null +++ b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol.js @@ -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(); + } +}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js deleted file mode 100644 index fc9842965..000000000 --- a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/log.js +++ /dev/null @@ -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'); - }); - } -}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js deleted file mode 100644 index 7bee47baf..000000000 --- a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/overview.js +++ /dev/null @@ -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'); - }); - } -}); diff --git a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js b/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js deleted file mode 100644 index 8cfd2a4d7..000000000 --- a/luci-app-fancontrol/htdocs/luci-static/resources/view/fancontrol/settings.js +++ /dev/null @@ -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(); - } -}); diff --git a/luci-app-fancontrol/po/templates/fancontrol.pot b/luci-app-fancontrol/po/templates/fancontrol.pot deleted file mode 100644 index dc142fe80..000000000 --- a/luci-app-fancontrol/po/templates/fancontrol.pot +++ /dev/null @@ -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 "" - diff --git a/luci-app-fancontrol/po/zh_Hans/fancontrol.po b/luci-app-fancontrol/po/zh_Hans/fancontrol.po index 7a378855d..be72c0edf 100644 --- a/luci-app-fancontrol/po/zh_Hans/fancontrol.po +++ b/luci-app-fancontrol/po/zh_Hans/fancontrol.po @@ -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 \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 数值写入系统日志。" diff --git a/luci-app-fancontrol/root/etc/config/fancontrol b/luci-app-fancontrol/root/etc/config/fancontrol deleted file mode 100644 index ebc31a9c6..000000000 --- a/luci-app-fancontrol/root/etc/config/fancontrol +++ /dev/null @@ -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' diff --git a/luci-app-fancontrol/root/etc/init.d/fancontrol b/luci-app-fancontrol/root/etc/init.d/fancontrol deleted file mode 100755 index 215c0a602..000000000 --- a/luci-app-fancontrol/root/etc/init.d/fancontrol +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh /etc/rc.common -# SPDX-License-Identifier: Apache-2.0 -# Copyright (C) 2026 Dante - -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}" -} diff --git a/luci-app-fancontrol/root/usr/sbin/fancontrold b/luci-app-fancontrol/root/usr/sbin/fancontrold deleted file mode 100755 index 2f958540b..000000000 --- a/luci-app-fancontrol/root/usr/sbin/fancontrold +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: Apache-2.0 -# Copyright (C) 2026 Dante - -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 diff --git a/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json b/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json index a25bcad4c..88d2f9e17 100644 --- a/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json +++ b/luci-app-fancontrol/root/usr/share/luci/menu.d/luci-app-fancontrol.json @@ -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" ] } } } diff --git a/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json b/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json index 5f9ac21fa..579af283e 100644 --- a/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json +++ b/luci-app-fancontrol/root/usr/share/rpcd/acl.d/luci-app-fancontrol.json @@ -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" ] } } } diff --git a/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol b/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol deleted file mode 100644 index c932dc37d..000000000 --- a/luci-app-fancontrol/root/usr/share/rpcd/ucode/luci.fancontrol +++ /dev/null @@ -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 }; diff --git a/luci-app-ports-status-mod/Makefile b/luci-app-ports-status-mod/Makefile deleted file mode 100644 index 7bd3aa901..000000000 --- a/luci-app-ports-status-mod/Makefile +++ /dev/null @@ -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 diff --git a/luci-app-ports-status-mod/htdocs/luci-static/resources/view/status/include/29_ports_custom.js b/luci-app-ports-status-mod/htdocs/luci-static/resources/view/status/include/29_ports_custom.js deleted file mode 100644 index 09cd82994..000000000 --- a/luci-app-ports-status-mod/htdocs/luci-static/resources/view/status/include/29_ports_custom.js +++ /dev/null @@ -1,1566 +0,0 @@ -'use strict'; -'require baseclass'; -'require fs'; -'require ui'; -'require uci'; -'require rpc'; -'require network'; -'require firewall'; -'require dom'; -'require poll'; - -(function() { - var style = document.createElement('style'); - style.textContent = [ - '@keyframes status-blink { 0%, 60%, 100% { opacity: 1; } 30% { opacity: 0.2; } }', - '.ports-operation-status {', - ' display: none;', - ' padding: 10px 14px;', - ' border: 1px solid var(--border-color-medium);', - ' border-radius: 5px;', - ' margin: 0 0 10px 0;', - ' font-size: 90%;', - '}', - '.ports-operation-status.active {', - ' display: block;', - '}', - '.ports-operation-status .spinning {', - ' display: inline-block;', - ' margin-right: 8px;', - '}', - 'html.theme-footstrap .ports-container .ifacebox {', - ' display: flex;', - ' flex-direction: column;', - ' align-items: stretch;', - ' box-sizing: border-box;', - ' text-align: center;', - ' min-width: 60px !important;', - ' max-width: 80px !important;', - ' width: 100%;', - ' margin: .2em auto !important;', - '}', - 'html.theme-footstrap .ports-container {', - ' grid-template-columns: repeat(auto-fit, minmax(60px, 80px)) !important;', - ' justify-content: start;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .ifacebox-head,', - 'html.theme-footstrap .ports-container .ifacebox > .ifacebox-body {', - ' display: block;', - ' width: 100%;', - ' box-sizing: border-box;', - ' float: none;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .ifacebox-head.cbi-tooltip-container {', - ' display: flex;', - '}', - 'html.theme-footstrap .ports-container .port-speed-body {', - ' padding: 0.3em 0.5em;', - ' line-height: 1.3;', - '}', - 'html.theme-footstrap .ports-container .port-speed-body img {', - ' display: block;', - ' margin: 0 auto 0.2em auto;', - '}', - 'html.theme-footstrap .ports-container .port-speed-body .port-speed-text {', - ' display: block;', - ' width: 100%;', - ' text-align: center;', - '}', - 'html.theme-footstrap .ports-container .port-status-body {', - ' display: block;', - ' clear: both;', - ' padding: 0.25em 0.5em;', - ' text-align: center;', - ' white-space: nowrap;', - ' border-top: 1px solid var(--border-color-medium);', - ' border-bottom: 1px solid var(--border-color-medium);', - '}', - 'html.theme-footstrap .ports-container .port-status-body > span {', - ' display: inline-block;', - ' vertical-align: middle;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .port-label {', - ' order: 1 !important;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .port-description {', - ' order: 2 !important;', - ' text-align: center;', - ' border-bottom: 1px solid var(--border-color-medium);', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .port-speed-body {', - ' order: 3 !important;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .ifacebox-head.cbi-tooltip-container {', - ' order: 4 !important;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .port-status-body {', - ' order: 5 !important;', - '}', - 'html.theme-footstrap .ports-container .ifacebox > .port-traffic-body {', - ' order: 6 !important;', - '}' - ].join('\n'); - document.head.appendChild(style); - - try { - var mediaBase = (window.L && L.env && L.env.mediaurlbase) || ''; - var isFootstrap = /footstrap/i.test(mediaBase) || - !!document.querySelector('link[rel="stylesheet"][href*="footstrap"]'); - - if (isFootstrap) - document.documentElement.classList.add('theme-footstrap'); - } catch (e) { - console.warn('Theme detection failed:', e); - } -})(); - -var callGetBuiltinEthernetPorts = rpc.declare({ - object: 'luci', - method: 'getBuiltinEthernetPorts', - expect: { result: [] } -}); - -var callWritePortConfig = rpc.declare({ - object: 'file', - method: 'write', - params: ['path', 'data'], - expect: { } -}); - -var callGetPortsStatus = rpc.declare({ - object: 'ports-status-mod', - method: 'getPortsStatus', - expect: { } -}); - -var callSetPortStatus = rpc.declare({ - object: 'ports-status-mod', - method: 'setPortStatus', - params: ['port', 'status'], - expect: { } -}); - -var USER_PORTS_FILE = '/etc/user_defined_ports.json'; -var USER_PORTS_BACKUP = '/etc/user_defined_ports.json.bak'; -var CONFIG_LOCK = false; -var isDragging = false; -var draggedElement = null; -var originalPorts = []; - -var _portsStatusTimer = null; - -function popTimeout(a, message, timeout, severity) { - ui.addTimeLimitedNotification(a, message, timeout, severity); -} - -function showPortsStatus(message) { - var statusBox = document.getElementById('ports-operation-status'); - if (!statusBox) return; - statusBox.innerHTML = ''; - var msgText = (message && message.textContent !== undefined) - ? message.textContent - : (typeof message === 'string' ? message : ''); - statusBox.appendChild(E('span', {'class': 'spinning'}, msgText)); - statusBox.classList.add('active'); -} - -function hidePortsStatus() { - var statusBox = document.getElementById('ports-operation-status'); - if (!statusBox) return; - statusBox.classList.remove('active'); -} - -function showPortsStatusTimeout(message, timeout) { - showPortsStatus(message); - if (_portsStatusTimer) clearTimeout(_portsStatusTimer); - _portsStatusTimer = setTimeout(function() { - hidePortsStatus(); - _portsStatusTimer = null; - }, timeout || 4000); -} - -function isString(v) -{ - return typeof(v) === 'string' && v !== ''; -} - -function resolveVLANChain(ifname, bridges, mapping) -{ - while (!mapping[ifname]) { - var m = ifname.match(/^(.+)\.([^.]+)$/); - - if (!m) - break; - - if (bridges[m[1]]) { - if (bridges[m[1]].vlan_filtering) - mapping[ifname] = bridges[m[1]].vlans[m[2]]; - else - mapping[ifname] = bridges[m[1]].ports; - } - else if (/^[0-9]{1,4}$/.test(m[2]) && m[2] <= 4095) { - mapping[ifname] = [ m[1] ]; - } - else { - break; - } - - ifname = m[1]; - } -} - -function buildVLANMappings(mapping) -{ - var bridge_vlans = uci.sections('network', 'bridge-vlan'), - vlan_devices = uci.sections('network', 'device'), - interfaces = uci.sections('network', 'interface'), - bridges = {}; - - /* find bridge VLANs */ - for (var i = 0, s; (s = bridge_vlans[i]) != null; i++) { - if (!isString(s.device) || !/^[0-9]{1,4}$/.test(s.vlan) || +s.vlan > 4095) - continue; - - var aliases = L.toArray(s.alias), - ports = L.toArray(s.ports), - br = bridges[s.device] = (bridges[s.device] || { ports: [], vlans: {}, vlan_filtering: true }); - - br.vlans[s.vlan] = []; - - for (var j = 0; j < ports.length; j++) { - var port = ports[j].replace(/:[ut*]+$/, ''); - - if (br.ports.indexOf(port) === -1) - br.ports.push(port); - - br.vlans[s.vlan].push(port); - } - - for (var j = 0; j < aliases.length; j++) - if (aliases[j] != s.vlan) - br.vlans[aliases[j]] = br.vlans[s.vlan]; - } - - /* find bridges, VLAN devices */ - for (var i = 0, s; (s = vlan_devices[i]) != null; i++) { - if (s.type == 'bridge') { - if (!isString(s.name)) - continue; - - var ports = L.toArray(s.ports), - br = bridges[s.name] || (bridges[s.name] = { ports: [], vlans: {}, vlan_filtering: false }); - - if (s.vlan_filtering == '0') - br.vlan_filtering = false; - else if (s.vlan_filtering == '1') - br.vlan_filtering = true; - - for (var j = 0; j < ports.length; j++) - if (br.ports.indexOf(ports[j]) === -1) - br.ports.push(ports[j]); - - mapping[s.name] = br.ports; - } - else if (s.type == '8021q' || s.type == '8021ad') { - if (!isString(s.name) || !isString(s.vid) || !isString(s.ifname)) - continue; - - /* parent device is a bridge */ - if (bridges[s.ifname]) { - /* parent bridge is VLAN enabled, device refers to VLAN ports */ - if (bridges[s.ifname].vlan_filtering) - mapping[s.name] = bridges[s.ifname].vlans[s.vid]; - - /* parent bridge is not VLAN enabled, device refers to all bridge ports */ - else - mapping[s.name] = bridges[s.ifname].ports; - } - - /* parent is a simple netdev */ - else { - mapping[s.name] = [ s.ifname ]; - } - - resolveVLANChain(s.ifname, bridges, mapping); - } - } - - /* resolve VLAN tagged interfaces in bridge ports */ - for (var brname in bridges) { - for (var i = 0; i < bridges[brname].ports.length; i++) - resolveVLANChain(bridges[brname].ports[i], bridges, mapping); - - for (var vid in bridges[brname].vlans) - for (var i = 0; i < bridges[brname].vlans[vid].length; i++) - resolveVLANChain(bridges[brname].vlans[vid][i], bridges, mapping); - } - - /* find implicit VLAN devices */ - for (var i = 0, s; (s = interfaces[i]) != null; i++) { - if (!isString(s.device)) - continue; - - resolveVLANChain(s.device, bridges, mapping); - } -} - -function resolveVLANPorts(ifname, mapping, seen) -{ - var ports = []; - - if (!seen) - seen = {}; - - if (mapping[ifname]) { - for (var i = 0; i < mapping[ifname].length; i++) { - if (!seen[mapping[ifname][i]]) { - seen[mapping[ifname][i]] = true; - ports.push.apply(ports, resolveVLANPorts(mapping[ifname][i], mapping, seen)); - } - } - } - else { - ports.push(ifname); - } - - return ports.sort(L.naturalCompare); -} - -function buildInterfaceMapping(zones, networks) { - var vlanmap = {}, - portmap = {}, - netmap = {}; - - buildVLANMappings(vlanmap); - - for (var i = 0; i < networks.length; i++) { - var l3dev = networks[i].getDevice(); - - if (!l3dev) - continue; - - var ports = resolveVLANPorts(l3dev.getName(), vlanmap); - - for (var j = 0; j < ports.length; j++) { - portmap[ports[j]] = portmap[ports[j]] || { networks: [], zones: [] }; - portmap[ports[j]].networks.push(networks[i]); - } - - netmap[networks[i].getName()] = networks[i]; - } - - for (var i = 0; i < zones.length; i++) { - var networknames = zones[i].getNetworks(); - - for (var j = 0; j < networknames.length; j++) { - if (!netmap[networknames[j]]) - continue; - - var l3dev = netmap[networknames[j]].getDevice(); - - if (!l3dev) - continue; - - var ports = resolveVLANPorts(l3dev.getName(), vlanmap); - - for (var k = 0; k < ports.length; k++) { - portmap[ports[k]] = portmap[ports[k]] || { networks: [], zones: [] }; - - if (portmap[ports[k]].zones.indexOf(zones[i]) === -1) - portmap[ports[k]].zones.push(zones[i]); - } - } - } - - return portmap; -} - -function formatSpeed(carrier, speed, duplex) { - if ((speed > 0) && duplex) { - var d = (duplex == 'half') ? '\u202f(H)' : '', - e = E('span', { 'title': _('Speed: %d Mbit/s, Duplex: %s').format(speed, duplex) }); - - switch (true) { - case (speed < 1000): - e.innerText = '%d\u202fM%s'.format(speed, d); - break; - case (speed == 1000): - e.innerText = '1\u202fGbE' + d; - break; - case (speed >= 1e6 && speed < 1e9): - e.innerText = '%f\u202fTbE'.format(speed / 1e6); - break; - case (speed >= 1e9): - e.innerText = '%f\u202fPbE'.format(speed / 1e9); - break; - default: e.innerText = '%f\u202fGbE'.format(speed / 1000); - } - - return e; - } - - return carrier ? _('Connected') : _('no link'); -} - -function formatStats(portdev) { - var stats = portdev._devstate('stats') || {}; - - return ui.itemlist(E('span'), [ - _('Received bytes'), '%1024mB'.format(stats.rx_bytes), - _('Received packets'), '%1000mPkts.'.format(stats.rx_packets), - _('Received multicast'), '%1000mPkts.'.format(stats.multicast), - _('Receive errors'), '%1000mPkts.'.format(stats.rx_errors), - _('Receive dropped'), '%1000mPkts.'.format(stats.rx_dropped), - - _('Transmitted bytes'), '%1024mB'.format(stats.tx_bytes), - _('Transmitted packets'), '%1000mPkts.'.format(stats.tx_packets), - _('Transmit errors'), '%1000mPkts.'.format(stats.tx_errors), - _('Transmit dropped'), '%1000mPkts.'.format(stats.tx_dropped), - - _('Collisions seen'), stats.collisions - ]); -} - -function renderNetworkBadge(network, zonename) { - var l3dev = network.getDevice(); - var span = E('span', { 'class': 'ifacebadge', 'style': 'margin:.125em 0' }, [ - E('span', { - 'class': 'zonebadge', - 'title': zonename ? _('Part of zone %q').format(zonename) : _('No zone assigned'), - 'style': firewall.getZoneColorStyle(zonename) - }, '\u202f'), - '\u202f', network.getName(), ': ' - ]); - - if (l3dev) - span.appendChild(E('img', { - 'title': l3dev.getI18n(), - 'src': L.resource('icons/%s%s.svg'.format(l3dev.getType(), l3dev.isUp() ? '' : '_disabled')) - })); - else - span.appendChild(E('em', _('(no interfaces attached)'))); - - return span; -} - -function renderNetworksTooltip(pmap) { - var res = [ null ], - zmap = {}; - - for (var i = 0; pmap && i < pmap.zones.length; i++) { - var networknames = pmap.zones[i].getNetworks(); - - for (var k = 0; k < networknames.length; k++) - zmap[networknames[k]] = pmap.zones[i].getName(); - } - - for (var i = 0; pmap && i < pmap.networks.length; i++) - res.push(E('br'), renderNetworkBadge(pmap.networks[i], zmap[pmap.networks[i].getName()])); - - if (res.length > 1) - res[0] = N_((res.length - 1) / 2, 'Part of network:', 'Part of networks:'); - else - res[0] = _('Port is not part of any network'); - - return E([], res); -} - -function validatePortsConfig(config) { - if (!config) { - console.error('Config is null or undefined'); - return false; - } - - if (!Array.isArray(config)) { - console.error('Config is not an array'); - return false; - } - - if (config.length === 0) { - console.error('Config is empty array'); - return false; - } - - for (var i = 0; i < config.length; i++) { - if (!config[i].device || typeof config[i].device !== 'string') { - console.error('Invalid device field at index', i); - return false; - } - } - - console.log('Config validation passed:', config.length, 'ports'); - return true; -} - -function loadUserPorts() { - return L.resolveDefault(fs.read(USER_PORTS_FILE), null).then(function(content) { - if (!content || content.trim() === '' || content.trim() === '[]') { - console.log('User ports file is empty or missing'); - return null; - } - try { - var parsed = JSON.parse(content); - if (Array.isArray(parsed) && parsed.length === 0) { - console.log('User ports file contains empty array'); - return null; - } - if (!validatePortsConfig(parsed)) { - console.error('Invalid ports configuration detected'); - ui.addNotification(null, E('p', _('Port config file is corrupted. Use "Restore backup .bak" from edit modal.')), 'error'); - return null; - } - console.log('Successfully loaded user ports config:', parsed.length, 'ports'); - return parsed; - } catch(e) { - console.error('Failed to parse user ports config:', e); - ui.addNotification(null, E('p', _('Port config parse error. Use "Restore backup .bak" from edit modal.')), 'error'); - return null; - } - }).catch(function(err) { - console.log('User ports file does not exist yet:', err); - return null; - }); -} - - -function setFileReadOnly(filepath) { - return fs.exec('/bin/chmod', ['444', filepath]).then(function(res) { - if (res.code === 0) { - console.log('File set to read-only (444):', filepath); - } else { - console.warn('Failed to set read-only on', filepath, ':', res.stderr); - } - }).catch(function(err) { - console.warn('chmod 444 error for', filepath, ':', err); - }); -} - -function ensureFileWritable(filepath) { - return fs.exec('/bin/chmod', ['644', filepath]).then(function(res) { - if (res.code === 0) { - console.log('File set to writable (644):', filepath); - return true; - } else { - return fs.exec('/bin/touch', [filepath]).then(function(touchRes) { - if (touchRes.code === 0) { - return fs.exec('/bin/chmod', ['644', filepath]).then(function() { return true; }); - } - console.warn('Could not create or chmod file:', filepath); - return false; - }); - } - }).catch(function(err) { - console.warn('ensureFileWritable error for', filepath, ':', err); - return false; - }); -} - -function verifyFileSaved(filepath, expectedContent) { - return L.resolveDefault(fs.read(filepath), null).then(function(content) { - if (!content) { - console.error('Verification failed: file empty after save'); - return false; - } - var ok = content.trim().substring(0, 80) === expectedContent.trim().substring(0, 80); - if (!ok) { - console.error('Verification failed: content mismatch'); - } - return ok; - }).catch(function() { return false; }); -} - -function saveUserPorts(ports) { - if (CONFIG_LOCK) { - console.warn('Save operation already in progress, skipping...'); - return Promise.reject(new Error('Save operation locked')); - } - - CONFIG_LOCK = true; - - if (!ports || !Array.isArray(ports) || ports.length === 0) { - CONFIG_LOCK = false; - console.error('Refusing to save empty or invalid configuration'); - ui.addNotification(null, E('p', _('Cannot save empty configuration')), 'error'); - return Promise.reject(new Error('Invalid configuration')); - } - - var config = ports.map(function(port) { - return { - device: port.device, - label: port.label || port.device, - role: port.role, - originalLabel: port.originalLabel || port.device, - description: port.description || '' - }; - }); - - var jsonContent = JSON.stringify(config, null, 2); - - return ensureFileWritable(USER_PORTS_FILE).then(function(writable) { - if (!writable) { - CONFIG_LOCK = false; - var msg = _('Cannot make config file writable. Check /etc permissions.'); - ui.addNotification(null, E('p', msg), 'error'); - throw new Error(msg); - } - return fs.write(USER_PORTS_FILE, jsonContent); - }).then(function() { - return verifyFileSaved(USER_PORTS_FILE, jsonContent).then(function(ok) { - if (!ok) { - CONFIG_LOCK = false; - var msg = _('Save verification failed - file content mismatch!'); - ui.addNotification(null, E('p', msg), 'error'); - throw new Error(msg); - } - console.log('Save verified successfully for', USER_PORTS_FILE); - return setFileReadOnly(USER_PORTS_FILE).then(function() { - CONFIG_LOCK = false; - return true; - }); - }); - }).catch(function(err) { - CONFIG_LOCK = false; - console.error('saveUserPorts failed:', err); - throw err; - }); -} - -function mergePortConfigs(detectedPorts, userConfig) { - if (!userConfig || !Array.isArray(userConfig) || userConfig.length === 0) { - console.log('No valid user config, using detected ports'); - return detectedPorts; - } - - var userMap = {}; - userConfig.forEach(function(p) { - if (p && p.device) { - userMap[p.device] = p; - } - }); - - var merged = []; - var addedDevices = {}; - - userConfig.forEach(function(userPort) { - if (!userPort || !userPort.device) return; - - var detected = detectedPorts.find(function(p) { return p.device === userPort.device; }); - if (detected) { - merged.push({ - device: detected.device, - role: detected.role, - netdev: detected.netdev, - label: userPort.label || detected.device, - originalLabel: userPort.originalLabel || detected.device, - description: userPort.description || '' - }); - addedDevices[userPort.device] = true; - } - }); - - detectedPorts.forEach(function(port) { - if (!addedDevices[port.device]) { - merged.push({ - device: port.device, - role: port.role, - netdev: port.netdev, - label: port.device, - originalLabel: port.originalLabel || port.device, - description: '' - }); - } - }); - - console.log('Merged config: detected=' + detectedPorts.length + ', user=' + userConfig.length + ', result=' + merged.length); - return merged; -} - -function showEditLabelModal(port, labelElement, descriptionElement, statusElement, ports) { - poll.stop(); - - var modalTitle = _('Edit Port Label'); - var currentLabel = port.label || port.device; - var currentDescription = port.description || ''; - var originalLabel = port.originalLabel || port.device; - - var statusRadioUp = E('input', { - 'type': 'radio', - 'name': 'port_status', - 'value': 'up', - 'id': 'port-status-up', - 'disabled': true - }); - - var statusRadioDown = E('input', { - 'type': 'radio', - 'name': 'port_status', - 'value': 'down', - 'id': 'port-status-down', - 'disabled': true - }); - - var isLanPort = false; - - L.resolveDefault(callGetPortsStatus(), {}).then(function(portsStatus) { - var deviceForStatus = port.device.split('@')[0]; - var operstate = portsStatus[deviceForStatus]; - - if (operstate !== undefined) { - isLanPort = true; - statusRadioUp.disabled = false; - statusRadioDown.disabled = false; - - var isCurrentlyEnabled = (operstate !== 'disabled'); - - if (isCurrentlyEnabled) { - statusRadioUp.checked = true; - } else { - statusRadioDown.checked = true; - } - port._currentOperstate = operstate; - } else { - var msgElement = document.getElementById('port-status-message'); - if (msgElement) { - msgElement.style.display = 'block'; - } - } - - console.log('Modal opened for port:', port.device); - console.log('Device for status lookup:', deviceForStatus); - console.log('Operstate from rpcd:', operstate); - console.log('isLanPort (enabled):', isLanPort); - }); - - var labelInputEl = E('input', { - 'type': 'text', - 'class': 'cbi-input-text', - 'value': currentLabel, - 'style': 'width: 100%; margin-bottom: 1em;', - 'placeholder': port.device, - 'maxlength': '9' - }); - - var descriptionInputEl = E('input', { - 'type': 'text', - 'class': 'cbi-input-text', - 'value': currentDescription, - 'style': 'width: 100%; margin-bottom: 1em;', - 'placeholder': _(''), - 'maxlength': '50' - }); - - var infoText = E('p', { 'style': 'margin: 0.5em 0; font-size: 90%; color: var(--text-color-secondary);' }, [ - _('Device')+': ', - E('strong', {}, port.device), - E('br'), - _('Original label')+': ', - E('strong', {}, originalLabel), - E('br'), - E('small', {}, _('Label max 9 chars, description max 50 chars.')), - E('br'), - E('small', {}, _('User settings are saved to the /etc/user_defined_ports.json file.')) - ]); - - var statusSection = [ - E('p', { 'style': 'margin-top: 1em;' }, _('Change port state')), - E('div', { 'style': 'margin-bottom: 1em;' }, [ - E('label', { - 'data-tooltip': _('Enable this port') - }, [ - statusRadioUp, - ' ', - _('Enable port') - ]), - ' \u00a0 ', - E('label', { - 'data-tooltip': _('Disable this port') - }, [ - statusRadioDown, - ' ', - _('Disable port') - ]) - ]), - E('div', { - 'id': 'port-status-message', - 'style': 'margin-top: 8px; padding: 8px; background: var(--background-color-medium); border: 1px solid var(--border-color-medium); border-radius: 4px; font-size: 12px; color: var(--text-color-secondary); display: none;' - }, [ - _('Changing port state is available only for LAN ports') - ]) - ]; - - var modalContent = E('div', {}, [ - E('p', {}, _('Enter new label for this port:')), - labelInputEl, - E('p', { 'style': 'margin-top: 1em;' }, _('Enter description (optional):')), - descriptionInputEl - ].concat(statusSection).concat([infoText])); - - var restoreBtn = E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function(ev) { - labelInputEl.value = originalLabel; - descriptionInputEl.value = ''; - ev.target.blur(); - labelInputEl.focus(); - } - }, _('Restore Original')); - - var handleDownloadConfig = function() { - L.resolveDefault(fs.read(USER_PORTS_FILE), null).then(function(content) { - if (content) { - var link = E('a', { - 'download': 'user_defined_ports.json', - 'href': URL.createObjectURL(new Blob([content], { - type: 'application/json' - })) - }); - link.click(); - URL.revokeObjectURL(link.href); - } else { - ui.addNotification(null, E('p', _('Configuration file not found')), 'warning'); - } - }).catch(function(err) { - ui.addNotification(null, E('p', _('Download error: %s').format(err.message)), 'error'); - }); - }; - - var handleUploadConfig = function(ev) { - var fileInput = E('input', { - 'type': 'file', - 'accept': '.json', - 'style': 'display: none', - 'change': function(e) { - var file = e.target.files[0]; - if (!file) return; - - var reader = new FileReader(); - reader.onload = function(event) { - try { - var config = JSON.parse(event.target.result); - - if (!Array.isArray(config)) { - throw new Error(_('Invalid configuration format')); - } - - if (!validatePortsConfig(config)) { - throw new Error(_('Configuration validation failed')); - } - - ui.showModal(_('Restore configuration'), [ - E('p', _('This will overwrite current ports configuration. Continue?')), - E('p', {}, [ - E('strong', {}, _('File contains:')), - ' ', config.length, ' ', _('ports') - ]), - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - ui.hideModal(); - poll.start(); - } - }, _('Cancel')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-positive', - 'click': function() { - fs.write(USER_PORTS_FILE, JSON.stringify(config, null, 2)).then(function() { - ui.hideModal(); - popTimeout(null, E('p', _('Configuration restored successfully. Reloading...')), 5000, 'info'); - setTimeout(function() { - window.location.reload(); - }, 1500); - }).catch(function(err) { - ui.hideModal(); - ui.addNotification(null, E('p', _('File restore failed: %s').format(err.message)), 'error'); - poll.start(); - }); - } - }, _('Restore')) - ]) - ]); - } catch(err) { - ui.addNotification(null, E('p', _('Invalid JSON file: %s').format(err.message)), 'error'); - } - }; - reader.readAsText(file); - } - }); - - document.body.appendChild(fileInput); - fileInput.click(); - document.body.removeChild(fileInput); - }; - - var handleRestoreBackup = function() { - L.resolveDefault(fs.read(USER_PORTS_BACKUP), null).then(function(backupContent) { - if (!backupContent) { - ui.addNotification(null, E('p', _('No backup file found')), 'warning'); - return; - } - - try { - var backupConfig = JSON.parse(backupContent); - if (!validatePortsConfig(backupConfig)) { - throw new Error('Invalid backup configuration'); - } - - ui.showModal(_('Restore from backup'), [ - E('p', _('Restore configuration from backup file? This will overwrite current settings.')), - E('p', {}, [ - E('strong', {}, _('Backup contains:')), - ' ', backupConfig.length, ' ', _('ports') - ]), - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - ui.hideModal(); - poll.start(); - } - }, _('Cancel')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - ensureFileWritable(USER_PORTS_FILE).then(function() { - return fs.write(USER_PORTS_FILE, backupContent); - }).then(function() { - return verifyFileSaved(USER_PORTS_FILE, backupContent); - }).then(function(ok) { - if (!ok) throw new Error('Verification failed'); - return setFileReadOnly(USER_PORTS_FILE); - }).then(function() { - ui.hideModal(); - popTimeout(null, E('p', _('Configuration restored from backup. Reloading...')), 5000, 'info'); - setTimeout(function() { window.location.reload(); }, 1500); - }).catch(function(err) { - ui.hideModal(); - ui.addNotification(null, E('p', _('Restore failed: %s').format(err.message)), 'error'); - poll.start(); - }); - } - }, _('Restore')) - ]) - ]); - } catch(e) { - ui.addNotification(null, E('p', _('Backup file is corrupted: %s').format(e.message)), 'error'); - } - }).catch(function(err) { - ui.addNotification(null, E('p', _('Error reading backup: %s').format(err.message)), 'error'); - }); - }; - - var handleCreateBackup = function() { - L.resolveDefault(fs.read(USER_PORTS_FILE), null).then(function(content) { - if (!content) { - ui.addNotification(null, E('p', _('No config file to backup')), 'warning'); - return; - } - try { - var parsed = JSON.parse(content); - if (!validatePortsConfig(parsed)) throw new Error('invalid'); - } catch(e) { - ui.addNotification(null, E('p', _('Config file is corrupted, cannot create backup')), 'error'); - return; - } - ensureFileWritable(USER_PORTS_BACKUP).then(function() { - return fs.write(USER_PORTS_BACKUP, content); - }).then(function() { - return verifyFileSaved(USER_PORTS_BACKUP, content); - }).then(function(ok) { - if (ok) { - return setFileReadOnly(USER_PORTS_BACKUP).then(function() { - popTimeout(null, E('p', _('Backup file created: %s').format(USER_PORTS_BACKUP)), 5000, 'info'); - }); - } else { - ui.addNotification(null, E('p', _('Backup write verification failed!')), 'error'); - } - }).catch(function(err) { - ui.addNotification(null, E('p', _('Backup creation failed: %s').format(err.message)), 'error'); - }); - }).catch(function(err) { - ui.addNotification(null, E('p', _('Error reading config: %s').format(err.message)), 'error'); - }); - }; - - var backupComboButton = new ui.ComboButton('_backup', { - '_backup': _('Create backup .bak'), - '_save': _('Save .json file'), - '_upload': _('Upload .json file'), - '_restore_backup': _('Restore backup .bak') - }, { - 'click': function(ev, name) { - if (name === '_backup') { - handleCreateBackup(); - } else if (name === '_save') { - handleDownloadConfig(); - } else if (name === '_upload') { - handleUploadConfig(ev); - } else if (name === '_restore_backup') { - handleRestoreBackup(); - } - }, - 'classes': { - '_backup': 'cbi-button cbi-button-action', - '_save': 'cbi-button cbi-button-neutral', - '_upload': 'cbi-button cbi-button-neutral', - '_restore_backup': 'cbi-button cbi-button-negative' - } - }).render(); - - ui.showModal(modalTitle, [ - modalContent, - E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [ - E('div', {}, [ - backupComboButton - ]), - E('div', { 'class': 'right' }, [ - restoreBtn, - ' ', - E('button', { - 'class': 'cbi-button cbi-button-neutral', - 'click': function() { - ui.hideModal(); - poll.start(); - } - }, _('Cancel')), - ' ', - E('button', { - 'class': 'cbi-button cbi-button-positive', - 'click': function() { - var newLabel = labelInputEl.value.trim(); - var newDescription = descriptionInputEl.value.trim(); - - if (newLabel === '') { - newLabel = port.device; - } - - port.label = newLabel; - port.description = newDescription; - labelElement.textContent = newLabel; - - if (newDescription) { - descriptionElement.textContent = newDescription; - descriptionElement.style.display = 'block'; - } else { - descriptionElement.textContent = ''; - descriptionElement.style.display = 'none'; - } - - var newStatusEnabled = document.getElementById('port-status-up').checked; - var currentOperstate = port._currentOperstate || 'unknown'; - var isCurrentlyEnabled = (currentOperstate !== 'disabled'); - var statusChanged = isLanPort && (newStatusEnabled !== isCurrentlyEnabled); - - ui.showModal(null, E('p', { 'class': 'spinning' }, _('Saving configuration...'))); - - var promises = [saveUserPorts(ports)]; - - if (statusChanged) { - var newStatus = newStatusEnabled ? 'up' : 'down'; - promises.push( - L.resolveDefault(callSetPortStatus(port.device, newStatus), {}) - .then(function(res) { - if (res && res.success) { - var newOperstate = newStatusEnabled ? 'idle' : 'disabled'; - var statusColor = '#888'; - var statusText = _('Down'); - var statusAnimate = ''; - - if (newOperstate === 'disabled') { - statusColor = '#FF204E'; // Red - statusText = _('Disabled'); - } else if (newOperstate === 'idle') { - statusColor = '#FFF455'; // Yellow - statusText = _('Idle'); - } - - var statusDot = E('span', { - 'style': 'display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;background-color:' + statusColor + ';' + statusAnimate - }); - - dom.content(statusElement, [ statusDot, statusText ]); - port._operstate = newOperstate; - port._currentOperstate = newOperstate; - - var msg = newStatusEnabled ? _('Port has been enabled successfully') : _('Port has been disabled successfully'); - showPortsStatusTimeout(msg, 5000); - } else { - showPortsStatusTimeout(_('Failed to change port status'), 5000); - } - }) - .catch(function(err) { - showPortsStatusTimeout(_('Error changing port status'), 5000); - }) - ); - } - - Promise.all(promises).then(function() { - ui.hideModal(); - showPortsStatusTimeout(_('Port configuration saved successfully'), 5000); - poll.start(); - }).catch(function(err) { - ui.hideModal(); - poll.start(); - }); - } - }, _('Save')) - ]) - ]) - ]); - - setTimeout(function() { - labelInputEl.focus(); - labelInputEl.select(); - }, 100); - - var handleKeydown = function(ev) { - if (ev.key === 'Enter') { - ev.preventDefault(); - var saveBtn = modalContent.parentNode.querySelector('.cbi-button-positive'); - if (saveBtn) { - saveBtn.click(); - } - } else if (ev.key === 'Escape') { - ev.preventDefault(); - ui.hideModal(); - poll.start(); - } - }; - - labelInputEl.addEventListener('keydown', handleKeydown); - descriptionInputEl.addEventListener('keydown', handleKeydown); -} - -function makeEditable(element, descriptionElement, statusElement, port, ports) { - element.style.cursor = 'pointer'; - element.title = _('Click to edit label'); - - element.addEventListener('click', function(ev) { - if (isDragging) - return; - - ev.stopPropagation(); - ev.preventDefault(); - - showEditLabelModal(port, element, descriptionElement, statusElement, ports); - }); -} - -function makeDraggable(element, port, container, ports) { - var dragHandle = E('div', { - 'class': 'drag-handle', - 'style': 'position: absolute; top: 0; left: 0; right: 0; bottom: 0; cursor: move; z-index: 1; pointer-events: none;', - 'title': _('Hold to drag and reorder') - }); - - element.style.position = 'relative'; - element.appendChild(dragHandle); - - var clickTimer = null; - var clickStart = null; - var hasMoved = false; - var isTouch = false; - - function startDrag(ev) { - isDragging = true; - draggedElement = element; - poll.stop(); - - element.style.opacity = '0.5'; - element.style.zIndex = '1000'; - dragHandle.style.cursor = 'move'; - dragHandle.style.pointerEvents = 'auto'; - - document.body.style.cursor = 'move'; - - var placeholder = E('div', { - 'class': 'ifacebox drag-placeholder', - 'style': element.style.cssText + 'opacity: 0.3; border: 3px dashed var(--border-color-medium); background: var(--border-color-low);' - }); - - element.style.boxShadow = '0 5px 15px var(--border-color-strong)'; - - function onMouseMove(e) { - if (isTouch && e.cancelable) { - e.preventDefault(); - } - - var clientX = e.clientX; - if (e.touches && e.touches.length > 0) { - clientX = e.touches[0].clientX; - } - - var afterElement = getDragAfterElement(container, clientX); - if (afterElement == null) { - container.appendChild(placeholder); - } else { - container.insertBefore(placeholder, afterElement); - } - } - - function onMouseUp(e) { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - document.removeEventListener('touchmove', onMouseMove, { passive: false }); - document.removeEventListener('touchend', onMouseUp); - - var clientX = e.clientX; - if (e.changedTouches && e.changedTouches.length > 0) { - clientX = e.changedTouches[0].clientX; - } - - var afterElement = getDragAfterElement(container, clientX); - if (afterElement == null) { - container.appendChild(element); - } else { - container.insertBefore(element, afterElement); - } - - if (placeholder.parentNode) - placeholder.parentNode.removeChild(placeholder); - - element.style.opacity = '1'; - element.style.zIndex = ''; - element.style.boxShadow = ''; - dragHandle.style.cursor = 'move'; - dragHandle.style.pointerEvents = 'none'; - document.body.style.cursor = ''; - - var newOrder = Array.from(container.children).map(function(el) { - return el.__port__; - }).filter(function(p) { return p; }); - - ports.length = 0; - newOrder.forEach(function(p) { ports.push(p); }); - - saveUserPorts(ports).then(function() { - isDragging = false; - draggedElement = null; - isTouch = false; - showPortsStatusTimeout(_('Port order saved'), 4000); - poll.start(); - }).catch(function(err) { - isDragging = false; - draggedElement = null; - isTouch = false; - poll.start(); - }); - } - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - document.addEventListener('touchmove', onMouseMove, { passive: false }); - document.addEventListener('touchend', onMouseUp); - } - - function getDragAfterElement(container, x) { - var draggableElements = Array.from(container.children).filter(function(child) { - return child !== draggedElement && child.classList.contains('ifacebox'); - }); - - return draggableElements.reduce(function(closest, child) { - var box = child.getBoundingClientRect(); - var offset = x - box.left - box.width / 2; - - if (offset < 0 && offset > closest.offset) { - return { offset: offset, element: child }; - } else { - return closest; - } - }, { offset: Number.NEGATIVE_INFINITY }).element; - } - - function onPointerDown(ev) { - if (ev.target.classList.contains('port-label') || ev.target.closest('.port-label')) { - return; - } - - isTouch = (ev.type === 'touchstart'); - - if (!isTouch && ev.button !== undefined && ev.button !== 0) { - return; - } - - var clientX = ev.clientX; - var clientY = ev.clientY; - - if (isTouch && ev.touches && ev.touches.length > 0) { - clientX = ev.touches[0].clientX; - clientY = ev.touches[0].clientY; - } - - clickStart = { x: clientX, y: clientY }; - hasMoved = false; - - var delay = isTouch ? 600 : 300; - - clickTimer = setTimeout(function() { - if (!hasMoved) { - startDrag(ev); - } - }, delay); - - if (isTouch) { - ev.preventDefault(); - } - } - - function onPointerUp(ev) { - if (clickTimer) { - clearTimeout(clickTimer); - clickTimer = null; - } - hasMoved = false; - clickStart = null; - } - - function onPointerMove(ev) { - if (clickTimer && clickStart) { - var clientX = ev.clientX; - var clientY = ev.clientY; - - if (isTouch && ev.touches && ev.touches.length > 0) { - clientX = ev.touches[0].clientX; - clientY = ev.touches[0].clientY; - } - - var distance = Math.sqrt( - Math.pow(clientX - clickStart.x, 2) + - Math.pow(clientY - clickStart.y, 2) - ); - - if (distance > 10) { - hasMoved = true; - clearTimeout(clickTimer); - clickTimer = null; - } - } - } - - element.addEventListener('mousedown', onPointerDown); - element.addEventListener('touchstart', onPointerDown, { passive: false }); - document.addEventListener('mouseup', onPointerUp); - document.addEventListener('touchend', onPointerUp); - document.addEventListener('mousemove', onPointerMove); - document.addEventListener('touchmove', onPointerMove, { passive: false }); -} - -return baseclass.extend({ - title: _('Port status'), - - load: function() { - return Promise.all([ - L.resolveDefault(callGetBuiltinEthernetPorts(), []), - L.resolveDefault(fs.read('/etc/board.json'), '{}'), - firewall.getZones(), - network.getNetworks(), - uci.load('network'), - loadUserPorts(), - L.resolveDefault(callGetPortsStatus(), {}) - ]); - }, - - render: function(data) { - if (L.hasSystemFeature('swconfig')) - return null; - - var board = JSON.parse(data[1]), - detected_ports = [], - port_map = buildInterfaceMapping(data[2], data[3]), - userConfig = data[5], - portsStatus = data[6] || {}; - - if (Array.isArray(data[0]) && data[0].length > 0) { - detected_ports = data[0].map(function(port) { - return { - device: port.device, - role: port.role, - netdev: network.instantiateDevice(port.device), - originalLabel: port.label || port.device - }; - }); - } - else { - if (L.isObject(board) && L.isObject(board.network)) { - for (var k = 'lan'; k != null; k = (k == 'lan') ? 'wan' : null) { - if (!L.isObject(board.network[k])) - continue; - - if (Array.isArray(board.network[k].ports)) - for (var i = 0; i < board.network[k].ports.length; i++) - detected_ports.push({ - role: k, - device: board.network[k].ports[i], - netdev: network.instantiateDevice(board.network[k].ports[i]), - originalLabel: board.network[k].ports[i] - }); - else if (typeof(board.network[k].device) == 'string') - detected_ports.push({ - role: k, - device: board.network[k].device, - netdev: network.instantiateDevice(board.network[k].device), - originalLabel: board.network[k].device - }); - } - } - } - - detected_ports.sort(function(a, b) { - return L.naturalCompare(a.device, b.device); - }); - - if (!userConfig || !validatePortsConfig(userConfig)) { - var initialConfig = detected_ports.map(function(p) { - return { - device: p.device, - label: p.device, - role: p.role, - originalLabel: p.originalLabel || p.device, - description: '' - }; - }); - - if (initialConfig.length > 0) { - console.log('Creating initial port configuration with', initialConfig.length, 'ports'); - saveUserPorts(initialConfig).then(function() { - console.log('Initial configuration created successfully'); - }).catch(function(err) { - console.error('Failed to create initial configuration:', err); - }); - - userConfig = initialConfig; - } - } else { - console.log('Using existing configuration with', userConfig.length, 'ports'); - } - - var known_ports = mergePortConfigs(detected_ports, userConfig); - originalPorts = known_ports.slice(); - - var statusBar = E('div', { - 'id': 'ports-operation-status', - 'class': 'ports-operation-status' - }); - - var container = E('div', { - 'class': 'ports-container', - 'style': 'display:grid;grid-template-columns:repeat(auto-fit, minmax(70px, 1fr));margin-bottom:1em' - }); - - var wrapper = E('div', {}, [ statusBar, container ]); - - known_ports.forEach(function(port) { - var speed = port.netdev.getSpeed(), - duplex = port.netdev.getDuplex(), - carrier = port.netdev.getCarrier(), - pmap = port_map[port.netdev.getName()], - pzones = (pmap && pmap.zones.length) ? pmap.zones.sort(function(a, b) { return L.naturalCompare(a.getName(), b.getName()) }) : [ null ]; - - var labelDiv = E('div', { - 'class': 'ifacebox-head port-label', - 'style': 'font-weight:bold; position: relative; z-index: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0.25em 0.5em;' - }, [ port.label || port.device ]); - - var descriptionDiv = E('div', { - 'class': 'ifacebox-body port-description', - 'style': 'font-size:70%; color: var(--text-color-secondary); padding: 0.2em 0.5em; min-height: 1.2em; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; cursor: help; display: ' + (port.description ? 'block' : 'none'), - 'title': port.description || '' - }, [ port.description || '' ]); - - var deviceForStatus = port.device.split('@')[0]; - var operstate = portsStatus[deviceForStatus] || 'unknown'; - - var statusColor = '#ccc'; - var statusText = _('Down'); - var statusAnimate = ''; - - if (operstate === 'disabled') { - statusColor = '#FF204E'; // Red - statusText = _('Disabled'); - } else if (operstate === 'down') { - statusColor = '#ccc'; // Gray - statusText = _('Down'); - } else if (operstate === 'idle') { - statusColor = '#FFA500'; // Orange - idle - statusText = _('Idle'); - } else if (operstate === 'active') { - statusColor = '#39FF13'; // Light green - active - statusText = _('Active'); - statusAnimate = 'animation: status-blink 0.8s infinite;'; - } else { - statusColor = '#ccc'; - statusText = _('Unknown'); - } - - var statusDot = E('span', { - 'style': 'display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:4px;background-color:' + statusColor + ';' + statusAnimate - }); - - var statusElement = E('span', { - 'style': 'font-size: 85%; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;' - }, [ statusDot, statusText ]); - - port._operstate = operstate; - port._currentOperstate = operstate; - port._isLanPort = true; - - var portBox = E('div', { - 'class': 'ifacebox', - 'style': 'margin:.25em;min-width:70px;max-width:100px; user-select: none;' - }, [ - labelDiv, - descriptionDiv, - E('div', { - 'class': 'ifacebox-body port-speed-body', - 'style': 'overflow: hidden; text-overflow: ellipsis; white-space: nowrap;' - }, [ - E('img', { 'src': L.resource('icons/port_%s.svg').format(carrier ? 'up' : 'down') }), - E('br'), - E('span', { - 'class': 'port-speed-text', - 'style': 'display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', - 'title': carrier ? - (speed > 0 && duplex ? _('Speed: %d Mibit/s, Duplex: %s').format(speed, duplex) : _('Connected')) : - _('no link') - }, [ - formatSpeed(carrier, speed, duplex) - ]) - ]), - E('div', { 'class': 'ifacebox-head cbi-tooltip-container', 'style': 'display:flex' }, [ - E([], pzones.map(function(zone) { - return E('div', { - 'class': 'zonebadge', - 'style': 'cursor:help;flex:1;height:3px;opacity:' + (carrier ? 1 : 0.25) + ';' + firewall.getZoneColorStyle(zone) - }); - })), - E('span', { 'class': 'cbi-tooltip left' }, [ renderNetworksTooltip(pmap) ]) - ]), - E('div', { - 'class': 'ifacebox-body port-status-body', - 'style': 'padding: 0.15em 0.5em; text-align: center; border-top: 1px solid var(--border-color-medium); border-bottom: 1px solid var(--border-color-medium); background: transparent; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;' - }, [ - statusElement - ]), - E('div', { 'class': 'ifacebox-body port-traffic-body' }, [ - E('div', { 'class': 'cbi-tooltip-container', 'style': 'text-align:left;font-size:80%' }, [ - '\u25b2\u202f%1024.1mB'.format(port.netdev.getTXBytes()), - E('br'), - '\u25bc\u202f%1024.1mB'.format(port.netdev.getRXBytes()), - E('span', { 'class': 'cbi-tooltip' }, formatStats(port.netdev)) - ]), - ]) - ]); - - portBox.__port__ = port; - - makeEditable(labelDiv, descriptionDiv, statusElement, port, known_ports); - makeDraggable(portBox, port, container, known_ports); - - container.appendChild(portBox); - }); - - return wrapper; - } -}); diff --git a/luci-app-ports-status-mod/po/cs/Ports_Status.po b/luci-app-ports-status-mod/po/cs/Ports_Status.po deleted file mode 100644 index c4dce2c82..000000000 --- a/luci-app-ports-status-mod/po/cs/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/da/Ports_Status.po b/luci-app-ports-status-mod/po/da/Ports_Status.po deleted file mode 100644 index 92282202e..000000000 --- a/luci-app-ports-status-mod/po/da/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/de/Ports_Status.po b/luci-app-ports-status-mod/po/de/Ports_Status.po deleted file mode 100644 index f05d72574..000000000 --- a/luci-app-ports-status-mod/po/de/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/es/Ports_Status.po b/luci-app-ports-status-mod/po/es/Ports_Status.po deleted file mode 100644 index ff7215e87..000000000 --- a/luci-app-ports-status-mod/po/es/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/fr/Ports_Status.po b/luci-app-ports-status-mod/po/fr/Ports_Status.po deleted file mode 100644 index e15765017..000000000 --- a/luci-app-ports-status-mod/po/fr/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/id/Ports_Status.po b/luci-app-ports-status-mod/po/id/Ports_Status.po deleted file mode 100644 index 7b2fc6447..000000000 --- a/luci-app-ports-status-mod/po/id/Ports_Status.po +++ /dev/null @@ -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 "" \ No newline at end of file diff --git a/luci-app-ports-status-mod/po/it/Ports_Status.po b/luci-app-ports-status-mod/po/it/Ports_Status.po deleted file mode 100644 index b86ee9486..000000000 --- a/luci-app-ports-status-mod/po/it/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/ja/Ports_Status.po b/luci-app-ports-status-mod/po/ja/Ports_Status.po deleted file mode 100644 index 8acf71e42..000000000 --- a/luci-app-ports-status-mod/po/ja/Ports_Status.po +++ /dev/null @@ -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 を作成" diff --git a/luci-app-ports-status-mod/po/ko/Ports_Status.po b/luci-app-ports-status-mod/po/ko/Ports_Status.po deleted file mode 100644 index e48b3f42a..000000000 --- a/luci-app-ports-status-mod/po/ko/Ports_Status.po +++ /dev/null @@ -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 생성" diff --git a/luci-app-ports-status-mod/po/nl/Ports_Status.po b/luci-app-ports-status-mod/po/nl/Ports_Status.po deleted file mode 100644 index fc55ebebc..000000000 --- a/luci-app-ports-status-mod/po/nl/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/pl/Ports_Status.po b/luci-app-ports-status-mod/po/pl/Ports_Status.po deleted file mode 100644 index 553a0762a..000000000 --- a/luci-app-ports-status-mod/po/pl/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/pt_BR/Ports_Status.po b/luci-app-ports-status-mod/po/pt_BR/Ports_Status.po deleted file mode 100644 index 49767370b..000000000 --- a/luci-app-ports-status-mod/po/pt_BR/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/ru/Ports_Status.po b/luci-app-ports-status-mod/po/ru/Ports_Status.po deleted file mode 100644 index ff72aad81..000000000 --- a/luci-app-ports-status-mod/po/ru/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/vi/Ports_Status.po b/luci-app-ports-status-mod/po/vi/Ports_Status.po deleted file mode 100644 index 65c2ef6cf..000000000 --- a/luci-app-ports-status-mod/po/vi/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/vn/Ports_Status.po b/luci-app-ports-status-mod/po/vn/Ports_Status.po deleted file mode 100644 index d51f2d15b..000000000 --- a/luci-app-ports-status-mod/po/vn/Ports_Status.po +++ /dev/null @@ -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 "" \ No newline at end of file diff --git a/luci-app-ports-status-mod/po/zh-cn b/luci-app-ports-status-mod/po/zh-cn deleted file mode 120000 index 8d69574dd..000000000 --- a/luci-app-ports-status-mod/po/zh-cn +++ /dev/null @@ -1 +0,0 @@ -zh_Hans \ No newline at end of file diff --git a/luci-app-ports-status-mod/po/zh_Hans/Ports_Status.po b/luci-app-ports-status-mod/po/zh_Hans/Ports_Status.po deleted file mode 100644 index 72fe95416..000000000 --- a/luci-app-ports-status-mod/po/zh_Hans/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/po/zh_Hant/Ports_Status.po b/luci-app-ports-status-mod/po/zh_Hant/Ports_Status.po deleted file mode 100644 index 15bb8bb8f..000000000 --- a/luci-app-ports-status-mod/po/zh_Hant/Ports_Status.po +++ /dev/null @@ -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" diff --git a/luci-app-ports-status-mod/root/etc/uci-defaults/ports_status_magic.sh b/luci-app-ports-status-mod/root/etc/uci-defaults/ports_status_magic.sh deleted file mode 100644 index c806e9685..000000000 --- a/luci-app-ports-status-mod/root/etc/uci-defaults/ports_status_magic.sh +++ /dev/null @@ -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 diff --git a/luci-app-ports-status-mod/root/usr/libexec/rpcd/ports-status-mod b/luci-app-ports-status-mod/root/usr/libexec/rpcd/ports-status-mod deleted file mode 100755 index 8dd12378d..000000000 --- a/luci-app-ports-status-mod/root/usr/libexec/rpcd/ports-status-mod +++ /dev/null @@ -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 diff --git a/luci-app-ports-status-mod/root/usr/share/rpcd/acl.d/luci-app-ports-status-mod.json b/luci-app-ports-status-mod/root/usr/share/rpcd/acl.d/luci-app-ports-status-mod.json deleted file mode 100644 index ea1d8883e..000000000 --- a/luci-app-ports-status-mod/root/usr/share/rpcd/acl.d/luci-app-ports-status-mod.json +++ /dev/null @@ -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": [ "*" ] - } - } - } -}