From f7d3c64ed4d5c87002804c66c1ac3a678fd55904 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Nov 2026 03:49:26 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=89=20Sync=202026-11-14=2003:49:26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dockerd/Config.in | 1 - dockerd/Makefile | 2 +- dockerd/files/dockerd.init | 4 +- ...tifd-migrate2 => 16_docker-netifd-migrate} | 2 + dockerd/git-short-commit.sh | 2 +- gately/Makefile | 4 +- 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-gately/Makefile | 4 +- .../LICENSE | 0 .../Makefile | 8 +- .../resources/view/v2raya/config.js | 0 .../luci-static/resources/view/v2raya/log.js | 0 .../resources/view/v2raya/overview.js | 0 .../po/templates/v2raya.pot | 0 .../root/usr/libexec/luci-app-v2raya/enable | 0 .../share/luci/menu.d/luci-app-v2raya.json | 0 .../usr/share/rpcd/acl.d/luci-app-v2raya.json | 0 v2raya/Makefile | 2 +- v2raya/files/v2raya-detect | 2 +- 32 files changed, 954 insertions(+), 1622 deletions(-) rename dockerd/files/etc/uci-defaults/{16_docker-netifd-migrate2 => 16_docker-netifd-migrate} (97%) 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 rename {luci-app-v2raya => luci-app-v2raya-nextgen}/LICENSE (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/Makefile (67%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/htdocs/luci-static/resources/view/v2raya/config.js (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/htdocs/luci-static/resources/view/v2raya/log.js (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/htdocs/luci-static/resources/view/v2raya/overview.js (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/po/templates/v2raya.pot (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/root/usr/libexec/luci-app-v2raya/enable (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/root/usr/share/luci/menu.d/luci-app-v2raya.json (100%) rename {luci-app-v2raya => luci-app-v2raya-nextgen}/root/usr/share/rpcd/acl.d/luci-app-v2raya.json (100%) diff --git a/dockerd/Config.in b/dockerd/Config.in index a5a8b20cc..a9cf1f4c9 100644 --- a/dockerd/Config.in +++ b/dockerd/Config.in @@ -15,7 +15,6 @@ config DOCKER_CGROUP_OPTIONS select KERNEL_CGROUP_FREEZER select KERNEL_NET_CLS_CGROUP select KERNEL_CGROUP_NET_PRIO - select PACKAGE_cgroupfs-mount help Selects kernel options to enable CGroups V1. diff --git a/dockerd/Makefile b/dockerd/Makefile index 0b60e2a78..996990c57 100644 --- a/dockerd/Makefile +++ b/dockerd/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=dockerd PKG_VERSION:=29.6.1 -PKG_RELEASE:=12 +PKG_RELEASE:=13 PKG_LICENSE:=Apache-2.0 PKG_LICENSE_FILES:=LICENSE PKG_CPE_ID:=cpe:/a:mobyproject:moby diff --git a/dockerd/files/dockerd.init b/dockerd/files/dockerd.init index df7f8e158..1111bba7b 100755 --- a/dockerd/files/dockerd.init +++ b/dockerd/files/dockerd.init @@ -166,7 +166,7 @@ process_config() { config_get https_proxy proxies https_proxy "${https_proxy}" config_get no_proxy proxies no_proxy "${no_proxy}" config_get storage_driver globals storage_driver "" - config_get max_concurrent_downloads globals max_concurrent_downloads "" + config_get max_concurrent_downloads globals max_concurrent_downloads "0" . /usr/share/libubox/jshn.sh json_init @@ -198,7 +198,7 @@ process_config() { fi [ -z "${storage_driver}" ] || json_add_string "storage-driver" "${storage_driver}" - [ "${max_concurrent_downloads:-0}" -eq "0" ] || json_add_int "max-concurrent-downloads" "${max_concurrent_downloads}" + [ "${max_concurrent_downloads}" -eq "0" ] || json_add_int "max-concurrent-downloads" "${max_concurrent_downloads}" json_add_string "log-driver" "json-file" json_add_object "log-opts" json_add_string "max-size" "50m" diff --git a/dockerd/files/etc/uci-defaults/16_docker-netifd-migrate2 b/dockerd/files/etc/uci-defaults/16_docker-netifd-migrate similarity index 97% rename from dockerd/files/etc/uci-defaults/16_docker-netifd-migrate2 rename to dockerd/files/etc/uci-defaults/16_docker-netifd-migrate index 52960d21d..19aa87708 100644 --- a/dockerd/files/etc/uci-defaults/16_docker-netifd-migrate2 +++ b/dockerd/files/etc/uci-defaults/16_docker-netifd-migrate @@ -11,3 +11,5 @@ if [ -n "$DOCKER0_DEVICE" ]; then fi uci -q delete network.docker uci commit + +exit 0 diff --git a/dockerd/git-short-commit.sh b/dockerd/git-short-commit.sh index 650ab8c82..6354b9e67 100755 --- a/dockerd/git-short-commit.sh +++ b/dockerd/git-short-commit.sh @@ -39,7 +39,7 @@ git init --quiet "${GIT_DIR}" if git fetch --depth 1 origin "${GIT_REF}"; then git checkout --detach FETCH_HEAD -- - git rev-parse --short HEAD + git rev-parse --short=7 HEAD break fi diff --git a/gately/Makefile b/gately/Makefile index 39b1a6991..2478601e8 100644 --- a/gately/Makefile +++ b/gately/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gately -PKG_VERSION:=0.6.6 -PKG_RELEASE:=34 +PKG_VERSION:=0.6.6.2 +PKG_RELEASE:=35 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)? diff --git a/luci-app-fancontrol/Makefile b/luci-app-fancontrol/Makefile index 650c33b03..18ded88a4 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:=7 -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:=8 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-gately/Makefile b/luci-app-gately/Makefile index d565447e9..4d428275b 100644 --- a/luci-app-gately/Makefile +++ b/luci-app-gately/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-gately -PKG_VERSION:=0.6.6 -PKG_RELEASE:=4 +PKG_VERSION:=0.6.6.2 +PKG_RELEASE:=5 PKG_MAINTAINER:=wangwanjie PKG_LICENSE:=Apache-2.0 diff --git a/luci-app-v2raya/LICENSE b/luci-app-v2raya-nextgen/LICENSE similarity index 100% rename from luci-app-v2raya/LICENSE rename to luci-app-v2raya-nextgen/LICENSE diff --git a/luci-app-v2raya/Makefile b/luci-app-v2raya-nextgen/Makefile similarity index 67% rename from luci-app-v2raya/Makefile rename to luci-app-v2raya-nextgen/Makefile index 36d47d35f..0e8378a07 100644 --- a/luci-app-v2raya/Makefile +++ b/luci-app-v2raya-nextgen/Makefile @@ -6,17 +6,17 @@ include $(TOPDIR)/rules.mk -LUCI_TITLE:=LuCI support for v2rayA +LUCI_TITLE:=LuCI support for v2rayA (NextGen) LUCI_DEPENDS:=+luci-base +v2raya LUCI_PKGARCH:=all LUCI_MAINTAINER:=Amr Fateem # Date-based version (YY.DDD), the same scheme as the OpenWrt LuCI packages. -# OpenWrt's luci-app-v2raya is newer than 2.5.13 by version comparison, so -# this version must stay ahead of it. Re-date it when OpenWrt passes it. +PKG_VERSION:=26.317.1 +PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 -CONFLICTS:=luci-app-v2raya-nextgen +CONFLICTS:=luci-app-v2raya include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js b/luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/config.js similarity index 100% rename from luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js rename to luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/config.js diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js b/luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/log.js similarity index 100% rename from luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js rename to luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/log.js diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js b/luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/overview.js similarity index 100% rename from luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js rename to luci-app-v2raya-nextgen/htdocs/luci-static/resources/view/v2raya/overview.js diff --git a/luci-app-v2raya/po/templates/v2raya.pot b/luci-app-v2raya-nextgen/po/templates/v2raya.pot similarity index 100% rename from luci-app-v2raya/po/templates/v2raya.pot rename to luci-app-v2raya-nextgen/po/templates/v2raya.pot diff --git a/luci-app-v2raya/root/usr/libexec/luci-app-v2raya/enable b/luci-app-v2raya-nextgen/root/usr/libexec/luci-app-v2raya/enable similarity index 100% rename from luci-app-v2raya/root/usr/libexec/luci-app-v2raya/enable rename to luci-app-v2raya-nextgen/root/usr/libexec/luci-app-v2raya/enable diff --git a/luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json b/luci-app-v2raya-nextgen/root/usr/share/luci/menu.d/luci-app-v2raya.json similarity index 100% rename from luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json rename to luci-app-v2raya-nextgen/root/usr/share/luci/menu.d/luci-app-v2raya.json diff --git a/luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json b/luci-app-v2raya-nextgen/root/usr/share/rpcd/acl.d/luci-app-v2raya.json similarity index 100% rename from luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json rename to luci-app-v2raya-nextgen/root/usr/share/rpcd/acl.d/luci-app-v2raya.json diff --git a/v2raya/Makefile b/v2raya/Makefile index 401025d3d..8bf3cf75f 100644 --- a/v2raya/Makefile +++ b/v2raya/Makefile @@ -14,7 +14,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=v2raya PKG_VERSION:=2.5.15 -PKG_RELEASE:=12 +PKG_RELEASE:=13 PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)? diff --git a/v2raya/files/v2raya-detect b/v2raya/files/v2raya-detect index 0e3c56fb9..8b311ee12 100644 --- a/v2raya/files/v2raya-detect +++ b/v2raya/files/v2raya-detect @@ -80,7 +80,7 @@ version_lt() { old_v2raya_pkg=0 pkg_has v2raya-nextgen && old_v2raya_pkg=1 pkg_has v2raya-core-nextgen && old_v2raya_pkg=1 -pkg_has luci-app-v2raya-nextgen && old_v2raya_pkg=1 +pkg_has luci-app-v2raya && old_v2raya_pkg=1 if pkg_has v2raya; then version_lt "$(pkg_version v2raya)" "2.5" && old_v2raya_pkg=1 fi