From 65e584daaa5101ec06b39712989356a27e626c49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Sep 2026 22:50:31 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=9E=20Sync=202026-09-06=2022:50:31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agentflow/Makefile | 2 +- .../shared-runtime-home-milestones.zh-CN.md | 84 - agentflow/files/agentflow.init | 35 +- agentflow/tests/test_mise_runtime_contract.py | 80 - airconnect/Makefile | 2 +- luci-app-agentflow/Makefile | 2 +- luci-app-agentflow/luasrc/model/agentflow.lua | 33 +- .../luasrc/model/cbi/agentflow.lua | 23 +- luci-app-agentflow/po/zh-cn/agentflow.po | 9 - luci-app-bandix/Makefile | 4 +- .../resources/view/bandix/index.js | 445 +++++- luci-app-bandix/po/es/bandix.po | 90 ++ luci-app-bandix/po/fr/bandix.po | 90 ++ luci-app-bandix/po/ja/bandix.po | 90 ++ luci-app-bandix/po/pl/bandix.po | 90 ++ luci-app-bandix/po/ru/bandix.po | 90 ++ luci-app-bandix/po/zh_Hans/bandix.po | 90 ++ luci-app-bandix/po/zh_Hant/bandix.po | 90 ++ .../root/usr/libexec/rpcd/luci.bandix | 134 ++ .../usr/share/rpcd/acl.d/luci-app-bandix.json | 10 +- luci-app-ddns/po/es/ddns.po | 8 +- luci-app-ddns/po/ga/ddns.po | 6 +- luci-app-ddns/po/ko/ddns.po | 20 +- luci-app-ddns/po/lt/ddns.po | 14 +- luci-app-ddns/po/ru/ddns.po | 4 +- luci-app-ddns/po/tr/ddns.po | 18 +- luci-app-ddns/po/zh_Hans/ddns.po | 11 +- luci-app-dockerman/po/cs/dockerman.po | 4 +- luci-app-dockerman/po/ga/dockerman.po | 4 +- luci-app-dockerman/po/id/dockerman.po | 22 +- luci-app-dockerman/po/ko/dockerman.po | 6 +- luci-app-dockerman/po/lt/dockerman.po | 230 +-- luci-app-dockerman/po/pt_BR/dockerman.po | 12 +- luci-app-dockerman/po/ru/dockerman.po | 6 +- luci-app-dockerman/po/ta/dockerman.po | 8 +- luci-app-dockerman/po/tr/dockerman.po | 28 +- luci-app-dockerman/po/zh_Hans/dockerman.po | 6 +- luci-app-dockerman/po/zh_Hant/dockerman.po | 7 +- luci-app-ksmbd/po/es/ksmbd.po | 6 +- luci-app-ksmbd/po/lt/ksmbd.po | 4 +- luci-app-ksmbd/po/ru/ksmbd.po | 7 +- luci-app-ksmbd/po/zh_Hans/ksmbd.po | 9 +- luci-app-openclaw/CHANGELOG.md | 32 + luci-app-openclaw/Makefile | 4 +- luci-app-openclaw/README.md | 11 +- luci-app-openclaw/VERSION | 2 +- .../luasrc/controller/openclaw.lua | 312 +++- .../luasrc/model/cbi/openclaw/basic.lua | 56 +- .../luasrc/view/openclaw/advanced.htm | 266 ++- .../luasrc/view/openclaw/console.htm | 308 +++- .../luasrc/view/openclaw/status.htm | 11 +- luci-app-openclaw/root/etc/init.d/openclaw | 234 +-- luci-app-openclaw/root/usr/bin/openclaw-env | 1090 +++++++++++-- .../root/usr/libexec/openclaw-permissions.sh | 38 +- .../usr/libexec/openclaw-upgrade-state.sh | 1211 ++++++++++++++ .../usr/share/openclaw/model-presets.json | 4 +- .../share/openclaw/oc-config-interactive.js | 386 ++++- .../root/usr/share/openclaw/oc-config.sh | 464 ++++-- .../root/usr/share/openclaw/oc-menu-engine.js | 41 +- .../openclaw/openclaw-package-contract.js | 577 +++++++ .../root/usr/share/openclaw/ui/index.html | 194 ++- .../root/usr/share/openclaw/web-pty.js | 20 +- luci-app-openclaw/scripts/build-node-musl.sh | 122 +- .../scripts/postinstall-bundled-plugins.mjs | 50 + .../preinstall-package-manager-warning.mjs | 50 + .../fixtures/openclaw-2026.9.1-package.json | 17 + .../tests/fixtures/openclaw-schema-types.tsv | 3 +- .../tests/test_device_pairing_contract.sh | 89 ++ .../tests/test_luci_console_contract.sh | 9 +- .../tests/test_luci_csrf_contract.sh | 6 +- .../tests/test_model_presets_contract.sh | 10 + .../tests/test_node_packaging_contract.sh | 9 +- .../tests/test_openclaw_contracts.sh | 84 +- luci-app-openclaw/tests/test_openclaw_node.sh | 202 ++- .../tests/test_openclaw_runtime_contract.sh | 1419 +++++++++++++++++ .../tests/test_openclaw_upgrade_state.sh | 304 ++++ .../tests/test_pty_security_contract.sh | 8 + .../tests/test_status_contract.sh | 8 +- luci-app-passwall/Makefile | 2 +- .../root/usr/share/passwall/iptables.sh | 8 +- .../root/usr/share/passwall/nftables.sh | 6 +- luci-app-passwall2/Makefile | 2 +- .../root/usr/share/passwall2/lease2hosts.sh | 2 +- luci-app-samba4/po/es/samba4.po | 6 +- luci-app-samba4/po/lt/samba4.po | 4 +- luci-app-samba4/po/ru/samba4.po | 7 +- luci-app-samba4/po/zh_Hans/samba4.po | 9 +- luci-app-smartdns/Makefile | 2 +- luci-app-smartdns/po/cs/smartdns.po | 6 +- luci-app-smartdns/po/es/smartdns.po | 18 +- luci-app-smartdns/po/ga/smartdns.po | 10 +- luci-app-smartdns/po/pl/smartdns.po | 4 +- luci-app-smartdns/po/ru/smartdns.po | 4 +- luci-app-smartdns/po/zh_Hant/smartdns.po | 8 +- luci-app-transmission/po/lt/transmission.po | 6 +- luci-app-transmission/po/ru/transmission.po | 15 +- luci-app-transmission/po/tr/transmission.po | 8 +- luci-app-watchcat/po/es/watchcat.po | 8 +- luci-app-watchcat/po/ko/watchcat.po | 6 +- luci-app-watchcat/po/lt/watchcat.po | 4 +- luci-app-watchcat/po/ru/watchcat.po | 4 +- luci-app-watchcat/po/sv/watchcat.po | 6 +- luci-app-watchcat/po/zh_Hans/watchcat.po | 9 +- luci-theme-footstrap/Makefile | 2 +- .../styles/theme/30-tables.css | 19 + mise/Makefile | 8 +- mise/files/istore_runtime.sh | 121 -- mise/files/mise-exec | 7 + mise/files/mise-istore | 7 + mise/files/mise.config | 2 - mise/files/mise.sh | 52 + mise/files/mise.uci-default | 8 +- openwrt-bandix/Makefile | 6 +- teleproxy/Makefile | 4 +- wwand/Makefile | 6 +- 115 files changed, 8599 insertions(+), 1391 deletions(-) delete mode 100644 agentflow/docs/shared-runtime-home-milestones.zh-CN.md delete mode 100644 agentflow/tests/test_mise_runtime_contract.py create mode 100755 luci-app-openclaw/root/usr/libexec/openclaw-upgrade-state.sh create mode 100644 luci-app-openclaw/root/usr/share/openclaw/openclaw-package-contract.js create mode 100644 luci-app-openclaw/tests/fixtures/lifecycle-side-effect/scripts/postinstall-bundled-plugins.mjs create mode 100644 luci-app-openclaw/tests/fixtures/lifecycle-side-effect/scripts/preinstall-package-manager-warning.mjs create mode 100644 luci-app-openclaw/tests/fixtures/openclaw-2026.9.1-package.json create mode 100644 luci-app-openclaw/tests/test_device_pairing_contract.sh create mode 100644 luci-app-openclaw/tests/test_openclaw_runtime_contract.sh create mode 100644 luci-app-openclaw/tests/test_openclaw_upgrade_state.sh delete mode 100644 mise/files/istore_runtime.sh create mode 100644 mise/files/mise-exec create mode 100644 mise/files/mise-istore create mode 100644 mise/files/mise.sh diff --git a/agentflow/Makefile b/agentflow/Makefile index d881f0f8..bcd09364 100644 --- a/agentflow/Makefile +++ b/agentflow/Makefile @@ -15,7 +15,7 @@ AGENTFLOW_HASH_aarch64:=6d733ebc49b095bf8670a1b655a13bb8ad98c90ced19701b67d5cdd4 PKG_NAME:=agentflow PKG_VERSION:=0.3.0 -PKG_RELEASE:=7 +PKG_RELEASE:=8 AGENTFLOW_URL_FILE:=agentflow-linux-$(AGENTFLOW_ARCH) AGENTFLOW_DOWNLOAD:=$(AGENTFLOW_URL_FILE)-$(AGENTFLOW_HASH_$(ARCH)) diff --git a/agentflow/docs/shared-runtime-home-milestones.zh-CN.md b/agentflow/docs/shared-runtime-home-milestones.zh-CN.md deleted file mode 100644 index 71f0417a..00000000 --- a/agentflow/docs/shared-runtime-home-milestones.zh-CN.md +++ /dev/null @@ -1,84 +0,0 @@ -# AgentFlow 共享 Runtime HOME 实施里程碑 - -## 目标架构 - -AgentFlow 的应用数据和设备级开发运行时分离: - -- AgentFlow 数据目录:`/AgentFlow` -- 共享 Runtime HOME:`/Runtime/home` - -共享 Runtime HOME 由 `mise` 包提供公共 helper 初始化,AgentFlow 和后续运行时感知应用复用同一套 `HOME`、XDG、`MISE_*` 和 `PATH`。 - -## Milestone 1:公共 Runtime HOME 合约 - -目标: - -- `mise` 包安装 `/etc/config/mise`。 -- `mise` 包安装 `/lib/functions/istore_runtime.sh`。 -- helper 自动从 `quickstart.main.conf_dir` 推导 `/Runtime/home`。 -- helper 支持 `ISTORE_RUNTIME_CONF_DIR` 兜底,便于应用从自身 Configs 目录派生 Runtime。 -- helper 统一导出 `HOME`、`XDG_DATA_HOME`、`XDG_CACHE_HOME`、`XDG_CONFIG_HOME`、`XDG_STATE_HOME`、`MISE_DATA_DIR`、`MISE_CACHE_DIR`、`MISE_CONFIG_DIR`、`MISE_STATE_DIR` 和 `PATH`。 - -验收: - -- 新安装 `mise` 后,存在 `/etc/config/mise` 和 `/lib/functions/istore_runtime.sh`。 -- 已配置 quickstart 时,初始化路径为 `/Runtime/home`。 -- 未配置 quickstart 但应用提供 `ISTORE_RUNTIME_CONF_DIR` 时,初始化路径为 `$ISTORE_RUNTIME_CONF_DIR/Runtime/home`。 - -## Milestone 2:AgentFlow 接入共享 Runtime HOME - -目标: - -- AgentFlow 不再使用 `$data_dir/global` 作为 `HOME` 或 mise shim 根。 -- AgentFlow 启动时调用 `istore_runtime_export_env`。 -- `AGENT_FLOW_DATA` 继续固定在 AgentFlow 数据目录下的 `data` 子目录。 -- AgentFlow 服务进程及其子进程继承共享 Runtime HOME。 - -验收: - -- `/etc/init.d/agentflow start` 后,AgentFlow 进程环境中 `HOME=/Runtime/home`。 -- `MISE_DATA_DIR=$HOME/.local/share/mise`。 -- `PATH` 以 `$MISE_DATA_DIR/shims:$HOME/.local/bin` 开头。 -- AgentFlow 数据库仍写入 `/AgentFlow/data/db/db.sqlite`。 - -## Milestone 3:LuCI 可见性 - -目标: - -- AgentFlow LuCI 页面继续配置 AgentFlow 数据目录。 -- 页面展示推导出的共享 Runtime HOME。 -- 如果 `mise.runtime_dir` 被显式设置,页面展示其对应的 `home`。 - -验收: - -- quickstart `conf_dir=/mnt/vio3-1/Configs` 时,页面显示 `/mnt/vio3-1/Configs/Runtime/home`。 -- AgentFlow 数据目录仍显示 `/mnt/vio3-1/Configs/AgentFlow`。 - -## Milestone 4:迁移与兼容 - -目标: - -- 新安装默认使用共享 Runtime HOME。 -- 老安装中已有 `$data_dir/global/.local/share/mise` 时,不自动移动大目录。 -- 后续提供显式迁移命令或 LuCI 操作。 - -验收: - -- 升级不会删除或搬移旧 runtime 数据。 -- 用户确认迁移后,旧 mise data 可迁移到 `/Runtime/home/.local/share/mise`。 -- 迁移失败不会影响 AgentFlow 私有数据。 - -## Milestone 5:AgentFlow 后端项目环境集成 - -目标: - -- OpenWrt 层面完成共享 HOME 后,再在 AgentFlow 后端引入项目环境 wrapper。 -- coding agent 进程使用 `mise exec -- ...` 应用项目 `mise.toml`。 -- 运行期设置 `MISE_EXEC_AUTO_INSTALL=0`,缺失工具只在准备阶段安装。 - -验收: - -- 单 repo 项目中,agent 看到项目声明的 Node/Python/Go 版本。 -- 没有隐式运行期下载。 -- Codex/Claude/Kimi 等 agent 凭据继续来自共享 Runtime HOME。 -- 取消任务时 wrapper 和 agent 子进程都能退出。 diff --git a/agentflow/files/agentflow.init b/agentflow/files/agentflow.init index 079a7ddb..ce6c5179 100644 --- a/agentflow/files/agentflow.init +++ b/agentflow/files/agentflow.init @@ -10,22 +10,6 @@ get_config() { config_get port "$1" port "9000" } -load_runtime_env() { - local conf_dir - - [ -n "$data_dir" ] || return 1 - conf_dir="$(dirname "$data_dir")" - export ISTORE_RUNTIME_CONF_DIR="$conf_dir" - - if [ -r /lib/functions/istore_runtime.sh ]; then - . /lib/functions/istore_runtime.sh - istore_runtime_export_env - return $? - fi - - return 1 -} - start_service() { config_load agentflow config_foreach get_config agentflow @@ -35,11 +19,16 @@ start_service() { logger -t agentflow "missing executable: /usr/sbin/agentflow" return 1 } - mkdir -p "$data_dir" "$data_dir/data" || return 1 - if ! load_runtime_env; then - logger -t agentflow "shared runtime home is not configured" + [ -r /lib/functions/mise.sh ] || { + logger -t agentflow "missing mise environment helper" + return 1 + } + . /lib/functions/mise.sh + if ! istore_runtime_env; then + logger -t agentflow "failed to initialize mise environment" return 1 fi + mkdir -p "$data_dir" "$data_dir/data" || return 1 logger -t agentflow "starting AgentFlow on $host:$port" procd_open_instance @@ -49,14 +38,6 @@ start_service() { "AGENT_FLOW_HOST=$host" \ "AGENT_FLOW_PORT=$port" \ "HOME=$HOME" \ - "XDG_DATA_HOME=$XDG_DATA_HOME" \ - "XDG_CACHE_HOME=$XDG_CACHE_HOME" \ - "XDG_CONFIG_HOME=$XDG_CONFIG_HOME" \ - "XDG_STATE_HOME=$XDG_STATE_HOME" \ - "MISE_DATA_DIR=$MISE_DATA_DIR" \ - "MISE_CACHE_DIR=$MISE_CACHE_DIR" \ - "MISE_CONFIG_DIR=$MISE_CONFIG_DIR" \ - "MISE_STATE_DIR=$MISE_STATE_DIR" \ "PATH=$PATH" procd_set_param stdout 1 procd_set_param stderr 1 diff --git a/agentflow/tests/test_mise_runtime_contract.py b/agentflow/tests/test_mise_runtime_contract.py deleted file mode 100644 index 31841f45..00000000 --- a/agentflow/tests/test_mise_runtime_contract.py +++ /dev/null @@ -1,80 +0,0 @@ -from pathlib import Path -import unittest - - -THIS_FILE = Path(__file__).resolve() - - -def read(relative): - relative_path = Path(relative) - package = relative_path.parts[0] - app = package[9:] if package.startswith("luci-app-") else package - remainder = Path(*relative_path.parts[1:]) - - candidates = [ - THIS_FILE.parents[index] / relative_path - for index in range(2, min(len(THIS_FILE.parents), 6)) - ] - candidates.append(THIS_FILE.parents[3] / app / package / remainder) - - for candidate in candidates: - if candidate.exists(): - return candidate.read_text() - - searched = "\n".join(str(candidate) for candidate in candidates) - raise FileNotFoundError(f"{relative} not found; searched:\n{searched}") - - -class MiseRuntimeContractTest(unittest.TestCase): - def test_mise_package_installs_shared_runtime_contract(self): - makefile = read("mise/Makefile") - config = read("mise/files/mise.config") - defaults = read("mise/files/mise.uci-default") - helper = read("mise/files/istore_runtime.sh") - - self.assertIn("/etc/config/mise", makefile) - self.assertIn("/lib/functions/istore_runtime.sh", makefile) - self.assertIn("$(INSTALL_BIN) ./files/mise.uci-default", makefile) - self.assertIn("[ -f /etc/uci-defaults/mise ]", makefile) - - self.assertIn("config mise 'main'", config) - self.assertIn("option runtime_dir ''", config) - self.assertIn("option auto_discover '1'", config) - - self.assertIn(". /lib/functions/istore_runtime.sh", defaults) - self.assertIn("istore_runtime_init", defaults) - - self.assertIn("config_get conf_dir main conf_dir", helper) - self.assertIn('"$conf_dir" "$ISTORE_RUNTIME_DEFAULT_SUBDIR"', helper) - self.assertIn("ISTORE_RUNTIME_HOME_SUBDIR=\"home\"", helper) - self.assertIn("ISTORE_RUNTIME_CONF_DIR", helper) - self.assertIn('export HOME="$runtime_home"', helper) - self.assertIn('export XDG_DATA_HOME="$HOME/.local/share"', helper) - self.assertIn('export MISE_DATA_DIR="$XDG_DATA_HOME/mise"', helper) - self.assertIn('export PATH="$MISE_DATA_DIR/shims:$HOME/.local/bin:$PATH"', helper) - - def test_agentflow_consumes_shared_runtime_home(self): - init = read("agentflow/files/agentflow.init") - cbi = read("luci-app-agentflow/luasrc/model/cbi/agentflow.lua") - model = read("luci-app-agentflow/luasrc/model/agentflow.lua") - translations = read("luci-app-agentflow/po/zh-cn/agentflow.po") - - self.assertIn(". /lib/functions/istore_runtime.sh", init) - self.assertIn("istore_runtime_export_env", init) - self.assertIn('AGENT_FLOW_DATA=$data_dir/data', init) - self.assertIn('HOME=$HOME', init) - self.assertIn('MISE_DATA_DIR=$MISE_DATA_DIR', init) - self.assertNotIn("$data_dir/global", init) - self.assertNotIn(".local/share/mise/shims:$PATH", init) - - self.assertIn('translate("Shared runtime home")', cbi) - self.assertIn("agentflow_model.runtime_home", cbi) - self.assertIn('return runtime_dir .. "/home"', model) - self.assertIn('return conf_dir .. "/Runtime"', model) - - self.assertIn('msgid "Shared runtime home"', translations) - self.assertIn('msgstr "共享运行时 HOME"', translations) - - -if __name__ == "__main__": - unittest.main() diff --git a/airconnect/Makefile b/airconnect/Makefile index 6a70902f..f4bc3fea 100644 --- a/airconnect/Makefile +++ b/airconnect/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=airconnect -PKG_VERSION:=1.11.2 +PKG_VERSION:=1.11.3 PKG_RELEASE=1 PKG_SOURCE:=AirConnect-$(PKG_VERSION).zip diff --git a/luci-app-agentflow/Makefile b/luci-app-agentflow/Makefile index d5f2bcd9..3a42f8eb 100644 --- a/luci-app-agentflow/Makefile +++ b/luci-app-agentflow/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=AgentFlow PKG_VERSION:=1.0.0 -PKG_RELEASE:=4 +PKG_RELEASE:=5 LUCI_DEPENDS:=+agentflow +luci-compat LUCI_MINIFY_CSS:=0 LUCI_MINIFY_JS:=0 diff --git a/luci-app-agentflow/luasrc/model/agentflow.lua b/luci-app-agentflow/luasrc/model/agentflow.lua index 2e359d6f..6f60b5af 100644 --- a/luci-app-agentflow/luasrc/model/agentflow.lua +++ b/luci-app-agentflow/luasrc/model/agentflow.lua @@ -45,37 +45,14 @@ agentflow.find_paths = function(blocks, home_dirs) return paths, default_path end -local dirname = function(path) - path = (path or ""):match("^%s*(.-)%s*$") - path = path:gsub("/+$", "") - return path:match("^(.*)/[^/]+$") or "" -end - -agentflow.runtime_dir = function(data_dir, home_dirs) - local uci = require "luci.model.uci".cursor() - local configured = uci:get_first("mise", "mise", "runtime_dir", "") - if configured ~= nil and configured ~= "" then - return configured - end - - local conf_dir = dirname(data_dir) - if conf_dir == "" then - conf_dir = home_dirs["Configs"] - end +agentflow.runtime_dir = function(data_dir) + data_dir = (data_dir or ""):match("^%s*(.-)%s*$"):gsub("/+$", "") + local conf_dir = data_dir:match("^(.*)/[^/]+$") if conf_dir == nil or conf_dir == "" then - return "" + return nil end - return conf_dir .. "/Runtime" -end - -agentflow.runtime_home = function(data_dir, home_dirs) - local runtime_dir = agentflow.runtime_dir(data_dir, home_dirs) - if runtime_dir == nil or runtime_dir == "" then - return "" - end - - return runtime_dir .. "/home" + return conf_dir .. "/Runtime/home" end return agentflow diff --git a/luci-app-agentflow/luasrc/model/cbi/agentflow.lua b/luci-app-agentflow/luasrc/model/cbi/agentflow.lua index 5db104a6..02f99ed1 100644 --- a/luci-app-agentflow/luasrc/model/cbi/agentflow.lua +++ b/luci-app-agentflow/luasrc/model/cbi/agentflow.lua @@ -34,15 +34,22 @@ for _, val in pairs(paths) do end data_dir.default = default_path -local runtime_home = s:option(DummyValue, "_runtime_home", translate("Shared runtime home")) -runtime_home.description = translate("AgentFlow and other runtime-aware applications share this HOME. It is derived from the selected Configs directory unless mise has an explicit runtime directory.") -function runtime_home.cfgvalue(self, section) - local selected_data_dir = m.uci:get("agentflow", section, "data_dir") or default_path - local path = agentflow_model.runtime_home(selected_data_dir, home) - if path == "" then - return translate("Not configured") +function m.on_after_commit(self) + local uci = require "luci.model.uci".cursor() + local runtime_dir = uci:get("mise", "main", "runtime_dir") + if runtime_dir ~= nil and runtime_dir ~= "" then + return + end + + local saved_data_dir = uci:get_first("agentflow", "agentflow", "data_dir", "") + runtime_dir = agentflow_model.runtime_dir(saved_data_dir) + if runtime_dir == nil then + return + end + + if uci:set("mise", "main", "runtime_dir", runtime_dir) then + uci:commit("mise") end - return path end local port = s:option(Value, "port", translate("Listen port")) diff --git a/luci-app-agentflow/po/zh-cn/agentflow.po b/luci-app-agentflow/po/zh-cn/agentflow.po index 47d13bd7..41746741 100644 --- a/luci-app-agentflow/po/zh-cn/agentflow.po +++ b/luci-app-agentflow/po/zh-cn/agentflow.po @@ -43,15 +43,6 @@ msgstr "请选择硬盘作为数据目录" msgid "Required. AgentFlow stores its configuration, database and workspace data under this directory." msgstr "必需。AgentFlow 在此目录中保存配置、数据库和工作区数据。" -msgid "Shared runtime home" -msgstr "共享运行时 HOME" - -msgid "AgentFlow and other runtime-aware applications share this HOME. It is derived from the selected Configs directory unless mise has an explicit runtime directory." -msgstr "AgentFlow 和其他运行时感知应用共用此 HOME。除非 mise 显式设置了运行时目录,否则它会从所选 Configs 目录推导。" - -msgid "Not configured" -msgstr "未配置" - msgid "Status" msgstr "状态" diff --git a/luci-app-bandix/Makefile b/luci-app-bandix/Makefile index a7225a12..3f653745 100644 --- a/luci-app-bandix/Makefile +++ b/luci-app-bandix/Makefile @@ -10,8 +10,8 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix PKG_MAINTAINER:=timsaya -PKG_VERSION:=0.12.10 -PKG_RELEASE:=9 +PKG_VERSION:=0.12.11 +PKG_RELEASE:=10 include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js index 52f4d209..384f0668 100644 --- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js +++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js @@ -470,6 +470,26 @@ var callSetDefaultRateLimit = rpc.declare({ expect: {} }); +var callGetTrafficQuotas = rpc.declare({ + object: 'luci.bandix', + method: 'getTrafficQuotas', + expect: {} +}); + +var callSetTrafficQuota = rpc.declare({ + object: 'luci.bandix', + method: 'setTrafficQuota', + params: ['mac', 'minute_bytes', 'hourly_bytes', 'daily_bytes', 'weekly_bytes', 'monthly_bytes', 'total_bytes'], + expect: {} +}); + +var callDeleteTrafficQuota = rpc.declare({ + object: 'luci.bandix', + method: 'deleteTrafficQuota', + params: ['mac'], + expect: {} +}); + return view.extend({ load: function () { return Promise.all([ @@ -1902,6 +1922,74 @@ return view.extend({ .schedule-rules-info { } + + .traffic-quota-section { + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid ${scheme === 'dark' ? 'rgba(255,255,255,0.16)' : 'rgba(0,0,0,0.12)'}; + } + .traffic-quota-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + margin-bottom: 6px; + } + .traffic-quota-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px 14px; + margin-top: 12px; + } + .traffic-quota-input-row { + display: flex; + gap: 6px; + } + .traffic-quota-input-row .form-input { min-width: 0; flex: 1; } + .traffic-quota-input-row .cbi-input-select { width: 82px; flex-shrink: 0; } + .traffic-quota-usage { + margin-top: 12px; + padding: 10px 12px; + border-radius: 6px; + background: ${scheme === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}; + font-size: 0.8125rem; + } + .traffic-quota-usage-row { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 2px 0; + } + .traffic-quota-status { + font-size: 0.75rem; + font-weight: 600; + border-radius: 999px; + padding: 2px 8px; + color: #fff; + background: #10b981; + } + .traffic-quota-status.blocked { background: #ef4444; } + .traffic-quota-status.unconfigured { background: #6b7280; } + .traffic-quota-cell { + margin-top: 6px; + padding-top: 6px; + border-top: 1px dashed ${scheme === 'dark' ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.14)'}; + font-size: 0.75rem; + } + .traffic-quota-blocked-badge { + display: inline-block; + margin-left: 6px; + padding: 1px 6px; + border-radius: 999px; + color: #fff; + background: #ef4444; + font-size: 0.6875rem; + font-weight: 600; + } + + @media (max-width: 600px) { + .traffic-quota-grid { grid-template-columns: 1fr; } + } /* 统计区域样式 */ .traffic-stats-container { @@ -2904,7 +2992,7 @@ return view.extend({ E('th', {}, _('Device Info')), E('th', {}, _('LAN Traffic')), E('th', {}, _('WAN Traffic')), - E('th', {}, _('Rate Limit')), + E('th', {}, _('Rules / Quota')), E('th', {}, _('Actions')) ]) ]), @@ -3171,7 +3259,30 @@ return view.extend({ }); } - // 创建限速设置模态框 + function createQuotaInput(period, label) { + return E('div', { 'class': 'form-group', 'style': 'margin-bottom: 0;' }, [ + E('label', { 'class': 'form-label' }, label), + E('div', { 'class': 'traffic-quota-input-row' }, [ + E('input', { + 'type': 'number', + 'min': '0', + 'step': '0.01', + 'class': 'form-input', + 'id': 'traffic-quota-' + period + '-value', + 'placeholder': '0' + }), + E('select', { 'class': 'cbi-input-select', 'id': 'traffic-quota-' + period + '-unit' }, [ + E('option', { 'value': '1' }, 'B'), + E('option', { 'value': '1024' }, 'KB'), + E('option', { 'value': '1048576' }, 'MB'), + E('option', { 'value': '1073741824' }, 'GB'), + E('option', { 'value': '1099511627776' }, 'TB') + ]) + ]) + ]); + } + + // 创建设备设置模态框 var bandixModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'rate-limit-bandix-modal' }, [ E('div', { 'class': 'modal-content bandix-modal' }, [ E('div', { 'class': 'bandix-modal-body', }, [ @@ -3211,6 +3322,46 @@ return view.extend({ document.body.appendChild(bandixModal); + // 创建独立的流量配额模态框 + var trafficQuotaModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'traffic-quota-bandix-modal' }, [ + E('div', { 'class': 'modal-content bandix-modal' }, [ + E('div', { 'class': 'bandix-modal-header' }, [ + E('h3', { 'class': 'bandix-modal-title' }, _('Traffic Quota')) + ]), + E('div', { 'class': 'bandix-modal-body' }, [ + E('div', { 'class': 'device-summary', 'id': 'traffic-quota-device-summary' }), + E('div', { 'class': 'traffic-quota-section', 'style': 'margin-top: 0; padding-top: 0; border-top: none;' }, [ + E('div', { 'class': 'traffic-quota-header' }, [ + E('div', { 'style': 'font-weight: 600;' }, _('Quota Status')), + E('span', { 'class': 'traffic-quota-status', 'id': 'traffic-quota-status' }, _('Not configured')) + ]), + E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, + _('WAN upload and download are counted together. Enter 0 for unlimited.')), + E('div', { 'class': 'traffic-quota-grid' }, [ + createQuotaInput('minute', _('Minute Quota')), + createQuotaInput('hourly', _('Hourly Quota')), + createQuotaInput('daily', _('Daily Quota')), + createQuotaInput('weekly', _('Weekly Quota')), + createQuotaInput('monthly', _('Monthly Quota')), + createQuotaInput('total', _('Lifetime Quota')) + ]), + E('div', { 'class': 'traffic-quota-usage', 'id': 'traffic-quota-usage' }, [ + E('div', { 'style': 'opacity: 0.7;' }, _('No quota usage data')) + ]), + E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px;' }, [ + E('button', { 'class': 'cbi-button cbi-button-negative', 'id': 'traffic-quota-delete-btn', 'style': 'display: none;' }, _('Delete Quota')), + E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'traffic-quota-save-btn' }, _('Save Quota')) + ]) + ]) + ]), + E('div', { 'class': 'bandix-modal-footer' }, [ + E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'traffic-quota-modal-close' }, _('Close')) + ]) + ]) + ]); + + document.body.appendChild(trafficQuotaModal); + // 创建添加规则模态框 var addRuleModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'add-rule-bandix-modal' }, [ E('div', { 'class': 'modal-content bandix-modal' }, [ @@ -3946,9 +4097,17 @@ return view.extend({ // 模态框事件处理 var currentDevice = null; + var currentQuotaDevice = null; + var quotaFormDirty = false; var editingRule = null; var showRateLimitModal; + trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) { + input.addEventListener(input.tagName === 'SELECT' ? 'change' : 'input', function () { + quotaFormDirty = true; + }); + }); + // 显示模态框 showRateLimitModal = function (device) { currentDevice = device; @@ -3994,6 +4153,33 @@ return view.extend({ }, 300); } + function showTrafficQuotaModal(device) { + currentQuotaDevice = device; + quotaFormDirty = false; + var modal = document.getElementById('traffic-quota-bandix-modal'); + var deviceSummary = document.getElementById('traffic-quota-device-summary'); + var modalContent = [ + E('div', { 'class': 'device-summary-name' }, device.host || device.ip4), + E('div', { 'class': 'device-summary-details' }, device.ip4 + ' (' + device.mac + ')') + ]; + var modalBadges = buildDeviceUplinkChBadges(device); + if (modalBadges.length) { + modalContent.push(E('div', { 'class': 'device-summary-badges device-uplink-badges-wrap' }, modalBadges)); + } + deviceSummary.innerHTML = E('div', {}, modalContent).innerHTML; + renderTrafficQuotaForm(getTrafficQuotaForDevice(device.mac), false); + modal.classList.add('show'); + loadTrafficQuotaForm(); + } + + function hideTrafficQuotaModal() { + document.getElementById('traffic-quota-bandix-modal').classList.remove('show'); + setTimeout(function () { + currentQuotaDevice = null; + quotaFormDirty = false; + }, 300); + } + // 加载定时限速规则列表 function loadScheduleRules() { if (!currentDevice) return; @@ -4163,8 +4349,60 @@ return view.extend({ // 绑定 hostname 保存按钮事件 document.getElementById('hostname-save-btn').addEventListener('click', saveHostname); + document.getElementById('traffic-quota-save-btn').addEventListener('click', function () { + if (!currentQuotaDevice) return; + var mac = currentQuotaDevice.mac; + var values = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total'].map(readQuotaInput); + if (values.some(function (value) { return value === null; })) { + ui.addNotification(null, E('p', {}, _('Quota values must be non-negative and within the supported range.')), 'error'); + return; + } + var button = this; + button.disabled = true; + setTrafficQuotaFormDisabled(true); + callSetTrafficQuota(mac, values[0], values[1], values[2], values[3], values[4], values[5]).then(function (result) { + if (result && result.success === false) throw new Error(result.error || _('Failed to save traffic quota')); + return fetchAllTrafficQuotas(true); + }).then(function () { + if (currentQuotaDevice && currentQuotaDevice.mac === mac) { + quotaFormDirty = false; + renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), false); + } + updateDeviceData(); + }).catch(function (error) { + ui.addNotification(null, E('p', {}, _('Failed to save traffic quota') + ': ' + (error.message || error)), 'error'); + }).finally(function () { + button.disabled = false; + setTrafficQuotaFormDisabled(false); + }); + }); + + document.getElementById('traffic-quota-delete-btn').addEventListener('click', function () { + if (!currentQuotaDevice) return; + var mac = currentQuotaDevice.mac; + showConfirmDialog( + _('Delete Traffic Quota'), + _('Delete this quota and all of its accumulated usage?'), + function () { + callDeleteTrafficQuota(mac).then(function (result) { + if (result && result.success === false) throw new Error(result.error || _('Failed to delete traffic quota')); + return fetchAllTrafficQuotas(true); + }).then(function () { + if (currentQuotaDevice && currentQuotaDevice.mac === mac) { + quotaFormDirty = false; + renderTrafficQuotaForm(null, false); + } + updateDeviceData(); + }).catch(function (error) { + ui.addNotification(null, E('p', {}, _('Failed to delete traffic quota') + ': ' + (error.message || error)), 'error'); + }); + } + ); + }); + // 绑定关闭按钮事件 document.getElementById('bandix-modal-close').addEventListener('click', hideRateLimitModal); + document.getElementById('traffic-quota-modal-close').addEventListener('click', hideTrafficQuotaModal); // 历史趋势:状态与工具 var latestDevices = []; @@ -4175,6 +4413,154 @@ return view.extend({ var allScheduleRules = []; // 存储所有设备的定时限速规则 var isScheduleRulesLoading = false; // 防止轮询重入 + var allTrafficQuotas = []; + var trafficQuotasRequest = null; + var trafficQuotasLoadError = null; + + function fetchAllTrafficQuotas(forceRefresh) { + if (trafficQuotasRequest) { + if (!forceRefresh) return trafficQuotasRequest; + return trafficQuotasRequest.then(function () { + return fetchAllTrafficQuotas(true); + }); + } + trafficQuotasRequest = callGetTrafficQuotas().then(function (res) { + if (!res || res.success === false || res.status === 'error') { + throw new Error((res && (res.error || res.message)) || _('Failed to load traffic quotas')); + } + var quotas = []; + if (res && res.data && Array.isArray(res.data.quotas)) { + quotas = res.data.quotas; + } else if (res && Array.isArray(res.quotas)) { + quotas = res.quotas; + } + allTrafficQuotas = quotas; + trafficQuotasLoadError = null; + return quotas; + }).catch(function (error) { + console.error('Failed to fetch traffic quotas:', error); + allTrafficQuotas = []; + trafficQuotasLoadError = error && error.message ? error.message : _('Failed to load traffic quotas'); + return []; + }).finally(function () { + trafficQuotasRequest = null; + }); + return trafficQuotasRequest; + } + + function getTrafficQuotaForDevice(mac) { + for (var i = 0; i < allTrafficQuotas.length; i++) { + if (allTrafficQuotas[i] && allTrafficQuotas[i].mac === mac) { + return allTrafficQuotas[i]; + } + } + return null; + } + + function quotaPeriods(quota) { + return [ + { key: 'minute', label: _('Minute'), limit: quota.minute_bytes || 0, used: quota.minute_used_bytes || 0 }, + { key: 'hourly', label: _('Hourly'), limit: quota.hourly_bytes || 0, used: quota.hourly_used_bytes || 0 }, + { key: 'daily', label: _('Daily'), limit: quota.daily_bytes || 0, used: quota.daily_used_bytes || 0 }, + { key: 'weekly', label: _('Weekly'), limit: quota.weekly_bytes || 0, used: quota.weekly_used_bytes || 0 }, + { key: 'monthly', label: _('Monthly'), limit: quota.monthly_bytes || 0, used: quota.monthly_used_bytes || 0 }, + { key: 'total', label: _('Lifetime'), limit: quota.total_bytes || 0, used: quota.total_used_bytes || 0 } + ]; + } + + function setQuotaInput(period, bytes) { + var valueInput = document.getElementById('traffic-quota-' + period + '-value'); + var unitSelect = document.getElementById('traffic-quota-' + period + '-unit'); + if (!valueInput || !unitSelect) return; + var units = [1099511627776, 1073741824, 1048576, 1024, 1]; + var unit = period === 'minute' ? 1048576 : 1073741824; + if (bytes > 0) { + for (var i = 0; i < units.length; i++) { + if (bytes >= units[i] && bytes % units[i] === 0) { + unit = units[i]; + break; + } + } + } + unitSelect.value = String(unit); + valueInput.value = bytes > 0 ? String(bytes / unit) : '0'; + } + + function readQuotaInput(period) { + var value = Number(document.getElementById('traffic-quota-' + period + '-value').value || 0); + var unit = Number(document.getElementById('traffic-quota-' + period + '-unit').value || 1); + var bytes = Math.round(value * unit); + if (!Number.isFinite(bytes) || bytes < 0 || !Number.isSafeInteger(bytes)) return null; + return bytes; + } + + function setTrafficQuotaFormDisabled(disabled) { + trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) { + input.disabled = disabled; + }); + } + + function renderTrafficQuotaForm(quota, preserveInputs) { + if (!preserveInputs) { + var periods = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total']; + periods.forEach(function (period) { + setQuotaInput(period, quota ? (quota[period + '_bytes'] || 0) : 0); + }); + } + + var status = document.getElementById('traffic-quota-status'); + var usage = document.getElementById('traffic-quota-usage'); + var deleteBtn = document.getElementById('traffic-quota-delete-btn'); + if (!status || !usage || !deleteBtn) return; + + status.classList.toggle('blocked', !!(quota && quota.blocked)); + status.classList.toggle('unconfigured', !quota); + status.textContent = !quota ? (trafficQuotasLoadError ? _('Unavailable') : _('Not configured')) : (quota.blocked ? _('Blocked') : _('Active')); + deleteBtn.style.display = quota ? '' : 'none'; + usage.innerHTML = ''; + if (!quota) { + usage.appendChild(E('div', { 'style': 'opacity: 0.7;' }, trafficQuotasLoadError || _('Usage starts when a quota is first saved.'))); + return; + } + quotaPeriods(quota).forEach(function (period) { + usage.appendChild(E('div', { 'class': 'traffic-quota-usage-row' }, [ + E('span', {}, period.label), + E('span', {}, formatSize(period.used) + ' / ' + (period.limit > 0 ? formatSize(period.limit) : _('Unlimited'))) + ])); + }); + } + + function loadTrafficQuotaForm() { + if (!currentQuotaDevice) return Promise.resolve(); + var mac = currentQuotaDevice.mac; + renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty); + return fetchAllTrafficQuotas().then(function () { + if (currentQuotaDevice && currentQuotaDevice.mac === mac) { + renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty); + } + }); + } + + function buildTrafficQuotaCell(mac) { + var quota = getTrafficQuotaForDevice(mac); + if (!quota) return null; + var periods = quotaPeriods(quota).filter(function (period) { return period.limit > 0; }); + var mostUsed = null; + periods.forEach(function (period) { + var ratio = period.used / period.limit; + if (!mostUsed || ratio > mostUsed.ratio) mostUsed = { period: period, ratio: ratio }; + }); + var text = quota.blocked ? _('Quota exceeded') : _('Quota active'); + if (mostUsed) { + text = mostUsed.period.label + ': ' + formatSize(mostUsed.period.used) + ' / ' + formatSize(mostUsed.period.limit); + } + return E('div', { + 'class': 'traffic-quota-cell', + 'style': quota.blocked ? 'color: #ef4444; font-weight: 600;' : '', + 'title': quota.blocked ? _('WAN traffic is blocked because a quota was reached.') : _('Traffic Quota') + }, text); + } + // 获取所有定时限速规则 function fetchAllScheduleRules() { if (isScheduleRulesLoading) return Promise.resolve(); @@ -5862,7 +6248,7 @@ return view.extend({ createSortableHeader(_('Device Info'), 'online'), createSplitHeader(_('LAN Traffic'), 'lan_speed', 'lan_traffic'), createSplitHeader(_('WAN Traffic'), 'wan_speed', 'wan_traffic'), - E('th', {}, _('Schedule Rules')), + E('th', {}, _('Rules / Quota')), E('th', {}, _('Actions')) ]) ]), @@ -5890,9 +6276,9 @@ return view.extend({ // 填充数据 filteredDevices.forEach(function (device) { var isOnline = isDeviceOnline(device); + var deviceQuota = getTrafficQuotaForDevice(device.mac); - // 默认使用窄主题样式 - var buttonText = '⚙'; + var buttonText = 'R'; var actionButton = E('button', { 'class': 'cbi-button cbi-button-action', @@ -5903,6 +6289,15 @@ return view.extend({ showRateLimitModal(device); }); + var quotaButton = E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'title': _('Traffic Quota') + }, 'Q'); + + quotaButton.addEventListener('click', function () { + showTrafficQuotaModal(device); + }); + var deleteButton = E('button', { 'class': 'cbi-button cbi-button-reset', 'title': _('Delete Device') @@ -5935,7 +6330,10 @@ return view.extend({ E('span', { 'class': 'device-status ' + (isOnline ? 'online' : 'offline') }), - device.host || '-' + device.host || '-', + deviceQuota && deviceQuota.blocked + ? E('span', { 'class': 'traffic-quota-blocked-badge' }, _('Quota blocked')) + : '' ]), E('div', { 'class': 'device-ip' }, [ device.conn ? E('span', { @@ -6149,12 +6547,14 @@ return view.extend({ }; } - return E('td', {}, rulesInfo); + var quotaCell = buildTrafficQuotaCell(device.mac); + return E('td', {}, quotaCell ? [rulesInfo, quotaCell] : rulesInfo); })(), // 操作 E('td', { 'class': 'device-actions' }, [ actionButton, + quotaButton, deleteButton ]) ]); @@ -6193,6 +6593,16 @@ return view.extend({ }); return cardActionBtn; })(), + (function () { + var cardQuotaBtn = E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'title': _('Traffic Quota') + }, 'Q'); + cardQuotaBtn.addEventListener('click', function () { + showTrafficQuotaModal(device); + }); + return cardQuotaBtn; + })(), (function () { var cardDeleteBtn = E('button', { 'class': 'cbi-button cbi-button-reset', @@ -6279,6 +6689,14 @@ return view.extend({ rulesContent ]); })(), + (function () { + var quotaCell = buildTrafficQuotaCell(device.mac); + if (!quotaCell) return ''; + return E('div', { 'class': 'device-card-section' }, [ + E('div', { 'class': 'device-card-section-label' }, _('Traffic Quota')), + quotaCell + ]); + })(), // LAN流量(直接显示,不需要展开/收起) E('div', { 'class': 'device-card-section', 'style': 'margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.1);' }, [ E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')), @@ -7499,12 +7917,25 @@ return view.extend({ }); }, 5000); + // 轮询获取流量配额状态(每1秒) + poll.add(function () { + return fetchAllTrafficQuotas().then(function () { + if (window.__bandixRenderTable) window.__bandixRenderTable(); + if (currentQuotaDevice) { + renderTrafficQuotaForm(getTrafficQuotaForDevice(currentQuotaDevice.mac), quotaFormDirty); + } + }); + }, 1); + // Traffic Statistics 不再自动刷新,改为手动查询 // 立即执行一次,不等待轮询 updateDeviceData(); refreshWhitelistStatus(); fetchAllScheduleRules(); + fetchAllTrafficQuotas().then(function () { + if (window.__bandixRenderTable) window.__bandixRenderTable(); + }); // 初始化时间范围查询功能 setTimeout(function () { diff --git a/luci-app-bandix/po/es/bandix.po b/luci-app-bandix/po/es/bandix.po index 9a5e5885..053d0a08 100644 --- a/luci-app-bandix/po/es/bandix.po +++ b/luci-app-bandix/po/es/bandix.po @@ -1258,3 +1258,93 @@ msgstr "ID de programa ancla de egreso TCX" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "Se usa cuando tc_order es before/after. Debe ser un ID de programa de egreso valido en la misma interfaz." + +msgid "Traffic Quota" +msgstr "Cuota de tráfico" + +msgid "Quota Status" +msgstr "Estado de la cuota" + +msgid "Minute Quota" +msgstr "Cuota por minuto" + +msgid "Hourly Quota" +msgstr "Cuota por hora" + +msgid "Daily Quota" +msgstr "Cuota diaria" + +msgid "Weekly Quota" +msgstr "Cuota semanal" + +msgid "Monthly Quota" +msgstr "Cuota mensual" + +msgid "Lifetime Quota" +msgstr "Cuota total acumulada" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "La subida y la descarga WAN se contabilizan juntas. Introduzca 0 para no establecer límite." + +msgid "Not configured" +msgstr "Sin configurar" + +msgid "Blocked" +msgstr "Bloqueado" + +msgid "Active" +msgstr "Activa" + +msgid "No quota usage data" +msgstr "No hay datos de uso de la cuota" + +msgid "Delete Quota" +msgstr "Eliminar cuota" + +msgid "Save Quota" +msgstr "Guardar cuota" + +msgid "Usage starts when a quota is first saved." +msgstr "El uso comienza a contabilizarse al guardar una cuota por primera vez." + +msgid "Minute" +msgstr "Minuto" + +msgid "Lifetime" +msgstr "Total acumulado" + +msgid "Quota exceeded" +msgstr "Cuota excedida" + +msgid "Quota active" +msgstr "Cuota activa" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "El tráfico WAN está bloqueado porque se alcanzó una cuota." + +msgid "Quota blocked" +msgstr "Bloqueado por cuota" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "Los valores de cuota deben ser no negativos y estar dentro del rango admitido." + +msgid "Failed to save traffic quota" +msgstr "No se pudo guardar la cuota de tráfico" + +msgid "Delete Traffic Quota" +msgstr "Eliminar cuota de tráfico" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "¿Eliminar esta cuota y todo su uso acumulado?" + +msgid "Failed to delete traffic quota" +msgstr "No se pudo eliminar la cuota de tráfico" + +msgid "Rules / Quota" +msgstr "Reglas / Cuota" + +msgid "Failed to load traffic quotas" +msgstr "No se pudieron cargar las cuotas de tráfico" + +msgid "Unavailable" +msgstr "No disponible" diff --git a/luci-app-bandix/po/fr/bandix.po b/luci-app-bandix/po/fr/bandix.po index f4dd364c..ca9b8b20 100644 --- a/luci-app-bandix/po/fr/bandix.po +++ b/luci-app-bandix/po/fr/bandix.po @@ -1258,3 +1258,93 @@ msgstr "ID du programme d ancrage egress TCX" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "Utilise lorsque tc_order est before/after. Doit etre un ID de programme egress valide sur la meme interface." + +msgid "Traffic Quota" +msgstr "Quota de trafic" + +msgid "Quota Status" +msgstr "État du quota" + +msgid "Minute Quota" +msgstr "Quota par minute" + +msgid "Hourly Quota" +msgstr "Quota par heure" + +msgid "Daily Quota" +msgstr "Quota journalier" + +msgid "Weekly Quota" +msgstr "Quota hebdomadaire" + +msgid "Monthly Quota" +msgstr "Quota mensuel" + +msgid "Lifetime Quota" +msgstr "Quota total cumulé" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "Le trafic montant et descendant WAN est comptabilisé ensemble. Saisissez 0 pour un trafic illimité." + +msgid "Not configured" +msgstr "Non configuré" + +msgid "Blocked" +msgstr "Bloqué" + +msgid "Active" +msgstr "Actif" + +msgid "No quota usage data" +msgstr "Aucune donnée d'utilisation du quota" + +msgid "Delete Quota" +msgstr "Supprimer le quota" + +msgid "Save Quota" +msgstr "Enregistrer le quota" + +msgid "Usage starts when a quota is first saved." +msgstr "Le suivi de l'utilisation commence lors du premier enregistrement d'un quota." + +msgid "Minute" +msgstr "Minute" + +msgid "Lifetime" +msgstr "Total cumulé" + +msgid "Quota exceeded" +msgstr "Quota dépassé" + +msgid "Quota active" +msgstr "Quota actif" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "Le trafic WAN est bloqué car un quota a été atteint." + +msgid "Quota blocked" +msgstr "Blocage par quota" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "Les valeurs de quota doivent être positives ou nulles et respecter la plage prise en charge." + +msgid "Failed to save traffic quota" +msgstr "Impossible d'enregistrer le quota de trafic" + +msgid "Delete Traffic Quota" +msgstr "Supprimer le quota de trafic" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "Supprimer ce quota et toutes ses données d'utilisation cumulées ?" + +msgid "Failed to delete traffic quota" +msgstr "Impossible de supprimer le quota de trafic" + +msgid "Rules / Quota" +msgstr "Règles / Quota" + +msgid "Failed to load traffic quotas" +msgstr "Impossible de charger les quotas de trafic" + +msgid "Unavailable" +msgstr "Indisponible" diff --git a/luci-app-bandix/po/ja/bandix.po b/luci-app-bandix/po/ja/bandix.po index 2387a50e..63c82c52 100644 --- a/luci-app-bandix/po/ja/bandix.po +++ b/luci-app-bandix/po/ja/bandix.po @@ -1258,3 +1258,93 @@ msgstr "TCX egressアンカープログラムID" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "tc_orderがbefore/afterのときに使用します。同じインターフェース上の有効なegressプログラムIDである必要があります。" + +msgid "Traffic Quota" +msgstr "トラフィッククォータ" + +msgid "Quota Status" +msgstr "クォータの状態" + +msgid "Minute Quota" +msgstr "1分あたりのクォータ" + +msgid "Hourly Quota" +msgstr "1時間あたりのクォータ" + +msgid "Daily Quota" +msgstr "1日あたりのクォータ" + +msgid "Weekly Quota" +msgstr "1週間あたりのクォータ" + +msgid "Monthly Quota" +msgstr "1か月あたりのクォータ" + +msgid "Lifetime Quota" +msgstr "累計クォータ" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "WANのアップロードとダウンロードは合算されます。無制限にするには0を入力してください。" + +msgid "Not configured" +msgstr "未設定" + +msgid "Blocked" +msgstr "ブロック中" + +msgid "Active" +msgstr "有効" + +msgid "No quota usage data" +msgstr "クォータ使用量データはありません" + +msgid "Delete Quota" +msgstr "クォータを削除" + +msgid "Save Quota" +msgstr "クォータを保存" + +msgid "Usage starts when a quota is first saved." +msgstr "クォータを初めて保存した時点から使用量の計測を開始します。" + +msgid "Minute" +msgstr "1分" + +msgid "Lifetime" +msgstr "累計" + +msgid "Quota exceeded" +msgstr "クォータ超過" + +msgid "Quota active" +msgstr "クォータ有効" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "クォータに達したためWANトラフィックがブロックされています。" + +msgid "Quota blocked" +msgstr "クォータによりブロック" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "クォータ値は0以上で、サポートされている範囲内である必要があります。" + +msgid "Failed to save traffic quota" +msgstr "トラフィッククォータを保存できませんでした" + +msgid "Delete Traffic Quota" +msgstr "トラフィッククォータを削除" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "このクォータと累積された使用量をすべて削除しますか?" + +msgid "Failed to delete traffic quota" +msgstr "トラフィッククォータを削除できませんでした" + +msgid "Rules / Quota" +msgstr "ルール / クォータ" + +msgid "Failed to load traffic quotas" +msgstr "トラフィッククォータを読み込めませんでした" + +msgid "Unavailable" +msgstr "利用不可" diff --git a/luci-app-bandix/po/pl/bandix.po b/luci-app-bandix/po/pl/bandix.po index 60f92426..2a354ea0 100644 --- a/luci-app-bandix/po/pl/bandix.po +++ b/luci-app-bandix/po/pl/bandix.po @@ -1258,3 +1258,93 @@ msgstr "ID programu kotwicy TCX egress" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "Uzywane, gdy tc_order to before/after. Musi to byc prawidlowe ID programu egress na tym samym interfejsie." + +msgid "Traffic Quota" +msgstr "Limit transferu" + +msgid "Quota Status" +msgstr "Stan limitu" + +msgid "Minute Quota" +msgstr "Limit minutowy" + +msgid "Hourly Quota" +msgstr "Limit godzinowy" + +msgid "Daily Quota" +msgstr "Limit dzienny" + +msgid "Weekly Quota" +msgstr "Limit tygodniowy" + +msgid "Monthly Quota" +msgstr "Limit miesięczny" + +msgid "Lifetime Quota" +msgstr "Łączny limit" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "Ruch wysyłany i pobierany przez WAN jest sumowany. Wpisz 0, aby wyłączyć limit." + +msgid "Not configured" +msgstr "Nie skonfigurowano" + +msgid "Blocked" +msgstr "Zablokowano" + +msgid "Active" +msgstr "Aktywny" + +msgid "No quota usage data" +msgstr "Brak danych o wykorzystaniu limitu" + +msgid "Delete Quota" +msgstr "Usuń limit" + +msgid "Save Quota" +msgstr "Zapisz limit" + +msgid "Usage starts when a quota is first saved." +msgstr "Naliczanie użycia rozpoczyna się po pierwszym zapisaniu limitu." + +msgid "Minute" +msgstr "Minuta" + +msgid "Lifetime" +msgstr "Łącznie" + +msgid "Quota exceeded" +msgstr "Limit przekroczony" + +msgid "Quota active" +msgstr "Limit aktywny" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "Ruch WAN jest zablokowany, ponieważ osiągnięto limit." + +msgid "Quota blocked" +msgstr "Blokada przez limit" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "Wartości limitu muszą być nieujemne i mieścić się w obsługiwanym zakresie." + +msgid "Failed to save traffic quota" +msgstr "Nie udało się zapisać limitu transferu" + +msgid "Delete Traffic Quota" +msgstr "Usuń limit transferu" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "Usunąć ten limit i wszystkie dane o naliczonym użyciu?" + +msgid "Failed to delete traffic quota" +msgstr "Nie udało się usunąć limitu transferu" + +msgid "Rules / Quota" +msgstr "Reguły / Limit" + +msgid "Failed to load traffic quotas" +msgstr "Nie udało się wczytać limitów transferu" + +msgid "Unavailable" +msgstr "Niedostępne" diff --git a/luci-app-bandix/po/ru/bandix.po b/luci-app-bandix/po/ru/bandix.po index 67d5b403..27cf3edb 100644 --- a/luci-app-bandix/po/ru/bandix.po +++ b/luci-app-bandix/po/ru/bandix.po @@ -1258,3 +1258,93 @@ msgstr "ID якорной программы TCX egress" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "Используется, когда tc_order имеет значение before/after. Должен быть корректный ID egress-программы на том же интерфейсе." + +msgid "Traffic Quota" +msgstr "Квота трафика" + +msgid "Quota Status" +msgstr "Состояние квоты" + +msgid "Minute Quota" +msgstr "Минутная квота" + +msgid "Hourly Quota" +msgstr "Часовая квота" + +msgid "Daily Quota" +msgstr "Суточная квота" + +msgid "Weekly Quota" +msgstr "Недельная квота" + +msgid "Monthly Quota" +msgstr "Месячная квота" + +msgid "Lifetime Quota" +msgstr "Общая квота" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "Входящий и исходящий трафик WAN учитываются вместе. Введите 0, чтобы отключить ограничение." + +msgid "Not configured" +msgstr "Не настроено" + +msgid "Blocked" +msgstr "Заблокировано" + +msgid "Active" +msgstr "Активна" + +msgid "No quota usage data" +msgstr "Нет данных об использовании квоты" + +msgid "Delete Quota" +msgstr "Удалить квоту" + +msgid "Save Quota" +msgstr "Сохранить квоту" + +msgid "Usage starts when a quota is first saved." +msgstr "Учет трафика начинается после первого сохранения квоты." + +msgid "Minute" +msgstr "Минута" + +msgid "Lifetime" +msgstr "Всего" + +msgid "Quota exceeded" +msgstr "Квота превышена" + +msgid "Quota active" +msgstr "Квота активна" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "Трафик WAN заблокирован, так как достигнута квота." + +msgid "Quota blocked" +msgstr "Блокировка по квоте" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "Значения квоты должны быть неотрицательными и находиться в поддерживаемом диапазоне." + +msgid "Failed to save traffic quota" +msgstr "Не удалось сохранить квоту трафика" + +msgid "Delete Traffic Quota" +msgstr "Удалить квоту трафика" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "Удалить эту квоту и все накопленные данные об использовании?" + +msgid "Failed to delete traffic quota" +msgstr "Не удалось удалить квоту трафика" + +msgid "Rules / Quota" +msgstr "Правила / Квота" + +msgid "Failed to load traffic quotas" +msgstr "Не удалось загрузить квоты трафика" + +msgid "Unavailable" +msgstr "Недоступно" diff --git a/luci-app-bandix/po/zh_Hans/bandix.po b/luci-app-bandix/po/zh_Hans/bandix.po index 8307d54a..0e09a773 100644 --- a/luci-app-bandix/po/zh_Hans/bandix.po +++ b/luci-app-bandix/po/zh_Hans/bandix.po @@ -1285,3 +1285,93 @@ msgstr "TCX 出站锚点程序 ID" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "当 tc_order 为 before/after 时使用。必须是同一接口上的有效出站程序 ID。" + +msgid "Traffic Quota" +msgstr "流量配额" + +msgid "Quota Status" +msgstr "配额状态" + +msgid "Hourly Quota" +msgstr "每小时配额" + +msgid "Daily Quota" +msgstr "每天配额" + +msgid "Weekly Quota" +msgstr "每周配额" + +msgid "Monthly Quota" +msgstr "每月配额" + +msgid "Lifetime Quota" +msgstr "累计总量配额" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "WAN 上传和下载合并计算;输入 0 表示不限量。" + +msgid "Not configured" +msgstr "未配置" + +msgid "Blocked" +msgstr "已阻断" + +msgid "Active" +msgstr "生效中" + +msgid "No quota usage data" +msgstr "暂无配额用量数据" + +msgid "Delete Quota" +msgstr "删除配额" + +msgid "Save Quota" +msgstr "保存配额" + +msgid "Usage starts when a quota is first saved." +msgstr "首次保存配额后开始累计用量。" + +msgid "Lifetime" +msgstr "累计" + +msgid "Quota exceeded" +msgstr "配额已用尽" + +msgid "Quota active" +msgstr "配额生效中" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "因达到流量配额,WAN 流量已被阻断。" + +msgid "Quota blocked" +msgstr "配额阻断" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "配额必须是支持范围内的非负数。" + +msgid "Failed to save traffic quota" +msgstr "保存流量配额失败" + +msgid "Delete Traffic Quota" +msgstr "删除流量配额" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "确定删除此配额及其全部累计用量吗?" + +msgid "Failed to delete traffic quota" +msgstr "删除流量配额失败" + +msgid "Rules / Quota" +msgstr "规则 / 配额" + +msgid "Failed to load traffic quotas" +msgstr "加载流量配额失败" + +msgid "Unavailable" +msgstr "不可用" + +msgid "Minute Quota" +msgstr "每分钟配额" + +msgid "Minute" +msgstr "每分钟" diff --git a/luci-app-bandix/po/zh_Hant/bandix.po b/luci-app-bandix/po/zh_Hant/bandix.po index 1d6a37d2..40d6f129 100644 --- a/luci-app-bandix/po/zh_Hant/bandix.po +++ b/luci-app-bandix/po/zh_Hant/bandix.po @@ -1258,3 +1258,93 @@ msgstr "TCX 出站錨點程式 ID" msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface." msgstr "當 tc_order 為 before/after 時使用。必須是同一介面上的有效出站程式 ID。" + +msgid "Traffic Quota" +msgstr "流量配額" + +msgid "Quota Status" +msgstr "配額狀態" + +msgid "Minute Quota" +msgstr "每分鐘配額" + +msgid "Hourly Quota" +msgstr "每小時配額" + +msgid "Daily Quota" +msgstr "每天配額" + +msgid "Weekly Quota" +msgstr "每週配額" + +msgid "Monthly Quota" +msgstr "每月配額" + +msgid "Lifetime Quota" +msgstr "累計總量配額" + +msgid "WAN upload and download are counted together. Enter 0 for unlimited." +msgstr "WAN 上傳和下載合併計算;輸入 0 表示不限量。" + +msgid "Not configured" +msgstr "未設定" + +msgid "Blocked" +msgstr "已阻斷" + +msgid "Active" +msgstr "生效中" + +msgid "No quota usage data" +msgstr "暫無配額用量資料" + +msgid "Delete Quota" +msgstr "刪除配額" + +msgid "Save Quota" +msgstr "儲存配額" + +msgid "Usage starts when a quota is first saved." +msgstr "首次儲存配額後開始累計用量。" + +msgid "Minute" +msgstr "每分鐘" + +msgid "Lifetime" +msgstr "累計" + +msgid "Quota exceeded" +msgstr "配額已用盡" + +msgid "Quota active" +msgstr "配額生效中" + +msgid "WAN traffic is blocked because a quota was reached." +msgstr "因達到流量配額,WAN 流量已被阻斷。" + +msgid "Quota blocked" +msgstr "配額阻斷" + +msgid "Quota values must be non-negative and within the supported range." +msgstr "配額必須是支援範圍內的非負數。" + +msgid "Failed to save traffic quota" +msgstr "儲存流量配額失敗" + +msgid "Delete Traffic Quota" +msgstr "刪除流量配額" + +msgid "Delete this quota and all of its accumulated usage?" +msgstr "確定刪除此配額及其全部累計用量嗎?" + +msgid "Failed to delete traffic quota" +msgstr "刪除流量配額失敗" + +msgid "Rules / Quota" +msgstr "規則 / 配額" + +msgid "Failed to load traffic quotas" +msgstr "載入流量配額失敗" + +msgid "Unavailable" +msgstr "無法使用" diff --git a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix index d4bdec52..f6a5677e 100755 --- a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix +++ b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix @@ -19,6 +19,7 @@ readonly BANDIX_TRAFFIC_USAGE_INCREMENTS_API="$BANDIX_API_BASE/api/traffic/usage readonly BANDIX_RATE_LIMIT_WHITELIST_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist" readonly BANDIX_RATE_LIMIT_WHITELIST_ENABLED_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist/enabled" readonly BANDIX_RATE_LIMIT_DEFAULT_API="$BANDIX_API_BASE/api/traffic/rate_limit/default" +readonly BANDIX_TRAFFIC_QUOTAS_API="$BANDIX_API_BASE/api/traffic/quotas" # 通用函数:创建简单的JSON响应 make_value() { @@ -134,6 +135,71 @@ set_default_rate_limit() { echo "$response" } +get_traffic_quotas() { + local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$response" ]; then + make_error "Failed to connect to Bandix service" + return + fi + echo "$response" +} + +set_traffic_quota() { + local mac="$1" + local minute_bytes="$2" + local hourly_bytes="$3" + local daily_bytes="$4" + local weekly_bytes="$5" + local monthly_bytes="$6" + local total_bytes="$7" + + if [ -z "$mac" ]; then + make_error "MAC address is required" + return + fi + for value in "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes"; do + case "$value" in + ''|*[!0-9]*) + make_error "Quota values must be non-negative integers" + return + ;; + esac + done + + local mac_escaped=$(escape_json_string "$mac") + local request_data="{ + \"mac\": \"$mac_escaped\", + \"minute_bytes\": $minute_bytes, + \"hourly_bytes\": $hourly_bytes, + \"daily_bytes\": $daily_bytes, + \"weekly_bytes\": $weekly_bytes, + \"monthly_bytes\": $monthly_bytes, + \"total_bytes\": $total_bytes + }" + local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then + make_success "Traffic quota saved successfully" + else + make_error "Failed to save traffic quota" + fi +} + +delete_traffic_quota() { + local mac="$1" + if [ -z "$mac" ]; then + make_error "MAC address is required" + return + fi + local mac_escaped=$(escape_json_string "$mac") + local request_data="{\"mac\": \"$mac_escaped\"}" + local response=$(curl -s --connect-timeout 3 --max-time 10 -X DELETE -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then + make_success "Traffic quota deleted successfully" + else + make_error "Failed to delete traffic quota" + fi +} + # 删除设备 delete_device() { local mac="$1" @@ -1348,6 +1414,23 @@ case "$1" in json_add_int "wan_rx_rate_limit" json_add_int "wan_tx_rate_limit" json_close_object + + json_add_object "getTrafficQuotas" + json_close_object + + json_add_object "setTrafficQuota" + json_add_string "mac" + json_add_int "minute_bytes" + json_add_int "hourly_bytes" + json_add_int "daily_bytes" + json_add_int "weekly_bytes" + json_add_int "monthly_bytes" + json_add_int "total_bytes" + json_close_object + + json_add_object "deleteTrafficQuota" + json_add_string "mac" + json_close_object json_dump json_cleanup @@ -1793,6 +1876,57 @@ case "$1" in fi set_default_rate_limit "$wan_rx_rate_limit" "$wan_tx_rate_limit" ;; + getTrafficQuotas) + get_traffic_quotas + ;; + setTrafficQuota) + mac="" + minute_bytes="" + hourly_bytes="" + daily_bytes="" + weekly_bytes="" + monthly_bytes="" + total_bytes="" + input="" + if read -t 1 -r input; then :; fi + if [ -n "$input" ]; then + mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)" + [ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)" + minute_bytes="$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)" + [ -z "$minute_bytes" ] && minute_bytes="$(echo "$input" | jsonfilter -e '$.minute_bytes' 2>/dev/null)" + hourly_bytes="$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)" + [ -z "$hourly_bytes" ] && hourly_bytes="$(echo "$input" | jsonfilter -e '$.hourly_bytes' 2>/dev/null)" + daily_bytes="$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)" + [ -z "$daily_bytes" ] && daily_bytes="$(echo "$input" | jsonfilter -e '$.daily_bytes' 2>/dev/null)" + weekly_bytes="$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)" + [ -z "$weekly_bytes" ] && weekly_bytes="$(echo "$input" | jsonfilter -e '$.weekly_bytes' 2>/dev/null)" + monthly_bytes="$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)" + [ -z "$monthly_bytes" ] && monthly_bytes="$(echo "$input" | jsonfilter -e '$.monthly_bytes' 2>/dev/null)" + total_bytes="$(echo "$input" | jsonfilter -e '$[6]' 2>/dev/null)" + [ -z "$total_bytes" ] && total_bytes="$(echo "$input" | jsonfilter -e '$.total_bytes' 2>/dev/null)" + else + mac="$3" + minute_bytes="$4" + hourly_bytes="$5" + daily_bytes="$6" + weekly_bytes="$7" + monthly_bytes="$8" + total_bytes="$9" + fi + set_traffic_quota "$mac" "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes" + ;; + deleteTrafficQuota) + mac="" + input="" + if read -t 1 -r input; then :; fi + if [ -n "$input" ]; then + mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)" + [ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)" + else + mac="$3" + fi + delete_traffic_quota "$mac" + ;; esac ;; esac diff --git a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json index 452ec7dd..7c8ea354 100644 --- a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json +++ b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json @@ -26,7 +26,8 @@ "installUpdate", "getTrafficUsageRanking", "getTrafficUsageIncrements", - "getRateLimitWhitelist" + "getRateLimitWhitelist", + "getTrafficQuotas" ] }, "uci": [ @@ -64,7 +65,10 @@ "addRateLimitWhitelist", "deleteRateLimitWhitelist", "setDefaultRateLimit", - "updateScheduleLimit" + "updateScheduleLimit", + "getTrafficQuotas", + "setTrafficQuota", + "deleteTrafficQuota" ] }, "uci": [ @@ -72,4 +76,4 @@ ] } } -} \ No newline at end of file +} diff --git a/luci-app-ddns/po/es/ddns.po b/luci-app-ddns/po/es/ddns.po index c7b1d7ff..2c532916 100644 --- a/luci-app-ddns/po/es/ddns.po +++ b/luci-app-ddns/po/es/ddns.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: luci-app-ddns 2.4.0-1\n" "POT-Creation-Date: 2016-01-30 11:07+0100\n" -"PO-Revision-Date: 2026-08-14 17:53+0000\n" +"PO-Revision-Date: 2026-08-24 16:47+0000\n" "Last-Translator: Franco Castillo \n" "Language-Team: Spanish \n" @@ -28,7 +28,7 @@ msgstr "Añadir nuevos servicios..." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:607 msgid "Advanced Settings" -msgstr "Ajustes avanzados" +msgstr "Configuración avanzada" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:392 msgid "Allow non-public IPs" @@ -36,7 +36,7 @@ msgstr "Permitir IPs no públicas" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:606 msgid "Basic Settings" -msgstr "Ajustes básicos" +msgstr "Configuración básica" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:903 msgid "Bind Network" @@ -280,7 +280,7 @@ msgstr "" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:265 msgid "Global Settings" -msgstr "Ajustes globales" +msgstr "Configuración global" #: applications/luci-app-ddns/root/usr/share/rpcd/acl.d/luci-app-ddns.json:3 msgid "Grant access to ddns procedures" diff --git a/luci-app-ddns/po/ga/ddns.po b/luci-app-ddns/po/ga/ddns.po index 67672b6f..25718e2a 100644 --- a/luci-app-ddns/po/ga/ddns.po +++ b/luci-app-ddns/po/ga/ddns.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-04-24 00:23+0000\n" +"PO-Revision-Date: 2026-09-02 17:27+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin \n" "Language-Team: Irish \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 :" "(n>6 && n<11) ? 3 : 4;\n" -"X-Generator: Weblate 5.17.1-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -157,7 +157,7 @@ msgstr "Ní thacaítear le hiarratais DNS trí TCP" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:924 msgid "DNS-Server" -msgstr "Freastalaí DNS" +msgstr "Freastalaí-DNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:401 msgid "Date format" diff --git a/luci-app-ddns/po/ko/ddns.po b/luci-app-ddns/po/ko/ddns.po index 7d042a6b..c446ea6a 100644 --- a/luci-app-ddns/po/ko/ddns.po +++ b/luci-app-ddns/po/ko/ddns.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-05 19:01+0000\n" +"PO-Revision-Date: 2026-08-26 18:51+0000\n" "Last-Translator: Hyeonjeong Lee \n" "Language-Team: Korean \n" @@ -8,7 +8,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -162,15 +162,15 @@ msgstr "날짜 형식" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:853 msgid "Defines the Web page to read systems IP-Address from." -msgstr "" +msgstr "시스템의 IP 주소를 가져올 웹페이지를 지정합니다." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:863 msgid "Defines the interface to read systems IP-Address from" -msgstr "" +msgstr "시스템의 IP 주소를 가져올 인터페이스를 지정합니다" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:845 msgid "Defines the network to read systems IP-Address from" -msgstr "" +msgstr "시스템의 IP 주소를 가져올 네트워크를 지정합니다" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:18 msgid "Disabled" @@ -234,7 +234,7 @@ msgstr "파일" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:711 msgid "Follow instructions found on their WEB page." -msgstr "" +msgstr "제공업체의 웹페이지 안내를 참고하세요." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:403 msgid "For supported codes look here" @@ -738,7 +738,7 @@ msgstr "" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:893 msgid "This will be autoset to the selected interface" -msgstr "" +msgstr "선택한 인터페이스로 자동 설정됩니다" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:608 msgid "Timer Settings" @@ -750,7 +750,7 @@ msgstr "URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:852 msgid "URL to detect" -msgstr "" +msgstr "IP 확인 URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:229 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:230 @@ -870,7 +870,7 @@ msgstr "일" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:789 msgid "directory or path/file" -msgstr "디렉토리 또는 경로/파일" +msgstr "디렉터리 또는 경로/파일을 지정하거나," #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1002 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1036 @@ -898,4 +898,4 @@ msgstr "초" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:793 msgid "to run HTTPS without verification of server certificates (insecure)" -msgstr "" +msgstr "를 입력하여 서버 인증서 검증 없이 HTTPS로 연결합니다. (안전하지 않음)" diff --git a/luci-app-ddns/po/lt/ddns.po b/luci-app-ddns/po/lt/ddns.po index b22d45c3..a751da9a 100644 --- a/luci-app-ddns/po/lt/ddns.po +++ b/luci-app-ddns/po/lt/ddns.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"PO-Revision-Date: 2026-05-22 21:11+0000\n" +"PO-Revision-Date: 2026-08-27 11:44+0000\n" "Last-Translator: Džiugas Januševičius \n" "Language-Team: Lithuanian \n" @@ -11,7 +11,7 @@ msgstr "" "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" -"X-Generator: Weblate 2026.6.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -183,7 +183,7 @@ msgstr "Apibrėžia tinklą, iš kurio reikia skaityti sistemos IP adresą" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:18 msgid "Disabled" -msgstr "Išjungta/Neįgalinta (-s/-i)" +msgstr "Išjungtas/Išgalintas" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:748 msgid "Domain" @@ -210,7 +210,7 @@ msgstr "Įjungti/Įgalinti saugią komunikacijos ryšį su „DDNS“ teikėju" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:612 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1140 msgid "Enabled" -msgstr "Įjungta/Įgalinta (-s/-i)" +msgstr "Įjungtas/Įgalintas" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:966 msgid "Error" @@ -519,7 +519,7 @@ msgstr "Nevieši ir pagal numatytuosius nustatymus užblokuoti IP (dgs.)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1121 msgid "Not Running" -msgstr "Neveikia" +msgstr "Nevykdomas/-a" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:964 msgid "Notice" @@ -623,7 +623,7 @@ msgstr "Registruotas IP" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:567 msgid "Reload" -msgstr "Perleidimas/-sti" +msgstr "Perleisti" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:550 msgid "Reload this service" @@ -662,7 +662,7 @@ msgstr "Paleisti tik kartą" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1120 msgid "Running" -msgstr "Veikia" +msgstr "Vykdomas/-a" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:813 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:870 diff --git a/luci-app-ddns/po/ru/ddns.po b/luci-app-ddns/po/ru/ddns.po index c61dfc75..f77d3470 100644 --- a/luci-app-ddns/po/ru/ddns.po +++ b/luci-app-ddns/po/ru/ddns.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: LuCI: ddns\n" "POT-Creation-Date: 2017-10-17 21:00+0300\n" -"PO-Revision-Date: 2026-08-14 12:12+0000\n" +"PO-Revision-Date: 2026-08-28 22:28+0000\n" "Last-Translator: SnIPeRSnIPeR " "\n" "Language-Team: Russian \n" +"PO-Revision-Date: 2026-08-23 20:27+0000\n" +"Last-Translator: umut \n" "Language-Team: Turkish \n" "Language: tr\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.14-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -134,7 +134,7 @@ msgstr "Özel güncelleme betiği" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:284 msgid "DDNS" -msgstr "" +msgstr "DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:207 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:279 @@ -432,6 +432,8 @@ msgstr "Ana Makine Adı Ara" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:807 msgid "Method used to determine the system IP-Address to send in updates" msgstr "" +"Güncellemelerin gönderileceği sistem IP adresini belirlemek için kullanılan " +"yöntem" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:491 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1124 @@ -468,7 +470,7 @@ msgstr "Ağ" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:888 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:906 msgid "Network on which the ddns-updater scripts will be started" -msgstr "DDns güncelleyici komut dosyalarının başlatılacağı ağ" +msgstr "DDNS güncelleyici komut dosyalarının başlatılacağı ağ" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:228 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1149 @@ -486,7 +488,7 @@ msgstr "Sonraki Güncelleme" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1145 msgid "Next Verify" -msgstr "" +msgstr "Sonraki Doğrulama" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:227 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1135 @@ -751,6 +753,8 @@ msgid "" "This is only a problem with multiple WAN interfaces and your DDNS provider " "is unreachable via one of them." msgstr "" +"Bu sorun, yalnızca birden fazla WAN arabirimi olduğunda ve DDNS " +"sağlayıcınıza bu arabirimlerden biri üzerinden ulaşılamadığında ortaya çıkar." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1085 msgid "This is the current content of the log file in %h for this service." @@ -795,7 +799,7 @@ msgstr "DDns Hizmetleri Listesini Güncelle" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:684 msgid "Update URL" -msgstr "" +msgstr "URL'yi Güncelle" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:709 msgid "Update URL for updating your DDNS Provider." diff --git a/luci-app-ddns/po/zh_Hans/ddns.po b/luci-app-ddns/po/zh_Hans/ddns.po index 2b550d9d..41266dd9 100644 --- a/luci-app-ddns/po/zh_Hans/ddns.po +++ b/luci-app-ddns/po/zh_Hans/ddns.po @@ -5,8 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: luci-app-ddns 2.4.0-1\n" "POT-Creation-Date: 2016-01-30 11:07+0100\n" -"PO-Revision-Date: 2026-04-18 17:10+0000\n" -"Last-Translator: DragonBluep \n" +"PO-Revision-Date: 2026-08-31 15:23+0000\n" +"Last-Translator: Hosted Weblate user 54392 " +"\n" "Language-Team: Chinese (Simplified Han script) \n" "Language: zh_Hans\n" @@ -14,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.17.1-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -22,7 +23,7 @@ msgstr "基于安全原因路径中不允许含有 \"../\" 。" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:763 msgid "A.k.a. the TOKEN at e.g. afraid.org" -msgstr "有的服务商使用 TOKEN 作为更新密码,例如 afraid.org" +msgstr "有的服务商也称作TOKEN,例如 afraid.org" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:458 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:526 @@ -61,7 +62,7 @@ msgstr "" msgid "" "BusyBox's nslookup in the current compiled version does not handle given DNS " "Servers correctly!" -msgstr "当前编译的 BusyBox nslookup 不支持自定义 DNS 服务器!" +msgstr "当前编译版本中的 BusyBox nslookup 无法正确处理给定的 DNS 服务器!" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:443 msgid "CA cert bundle file" diff --git a/luci-app-dockerman/po/cs/dockerman.po b/luci-app-dockerman/po/cs/dockerman.po index 4e185805..2a2fd4f4 100644 --- a/luci-app-dockerman/po/cs/dockerman.po +++ b/luci-app-dockerman/po/cs/dockerman.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-08-14 18:35+0000\n" -"Last-Translator: Pavel Borecki \n" +"PO-Revision-Date: 2026-08-27 18:20+0000\n" +"Last-Translator: Martin Olmer \n" "Language-Team: Czech \n" "Language: cs\n" diff --git a/luci-app-dockerman/po/ga/dockerman.po b/luci-app-dockerman/po/ga/dockerman.po index 627d8220..df4dfa03 100644 --- a/luci-app-dockerman/po/ga/dockerman.po +++ b/luci-app-dockerman/po/ga/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-08-16 17:38+0000\n" +"PO-Revision-Date: 2026-09-02 17:28+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin \n" "Language-Team: Irish \n" @@ -227,7 +227,7 @@ msgstr "Níl sonraí úsáide LAP ar fáil" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:678 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:169 msgid "CPUs" -msgstr "CPUanna" +msgstr "LAPanna" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1933 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:517 diff --git a/luci-app-dockerman/po/id/dockerman.po b/luci-app-dockerman/po/id/dockerman.po index 8f0a64d0..85655d53 100644 --- a/luci-app-dockerman/po/id/dockerman.po +++ b/luci-app-dockerman/po/id/dockerman.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-05-31 16:42+0000\n" -"Last-Translator: ferrysqo \n" +"PO-Revision-Date: 2026-08-18 10:04+0000\n" +"Last-Translator: Fiqri Ardyansyah \n" "Language-Team: Indonesian \n" "Language: id\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2026.6.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -1449,7 +1449,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:396 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1805 msgid "Pause" -msgstr "" +msgstr "Jeda" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:207 msgid "Pause this container" @@ -1785,7 +1785,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:88 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:145 msgid "Stopped" -msgstr "" +msgstr "Berhenti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1098 msgid "Streams:" @@ -1926,7 +1926,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:45 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:111 msgid "Total" -msgstr "" +msgstr "Total" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:170 msgid "Total Memory" @@ -1943,7 +1943,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:63 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Type" -msgstr "" +msgstr "Tipe" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1135 msgid "Type command here... (Ctrl+D to detach)" @@ -1961,7 +1961,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1682 msgid "Unknown error" -msgstr "" +msgstr "Kesalahan tidak dikenal" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:485 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:250 @@ -1988,7 +1988,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:428 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:948 msgid "Upload" -msgstr "Upload" +msgstr "Unggah" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1609 msgid "Uploading file to container…" @@ -2033,7 +2033,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:240 msgid "Version" -msgstr "" +msgstr "Versi" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:374 msgid "Volume (named)" @@ -2201,7 +2201,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:757 msgid "none" -msgstr "" +msgstr "tidak ada" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:337 msgid "oom" diff --git a/luci-app-dockerman/po/ko/dockerman.po b/luci-app-dockerman/po/ko/dockerman.po index f9d95f9b..80d89ef3 100644 --- a/luci-app-dockerman/po/ko/dockerman.po +++ b/luci-app-dockerman/po/ko/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-08-07 23:22+0000\n" +"PO-Revision-Date: 2026-09-02 17:27+0000\n" "Last-Translator: Hyeonjeong Lee \n" "Language-Team: Korean \n" @@ -1340,7 +1340,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:116 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:151 msgid "Now" -msgstr "" +msgstr "지금" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:679 msgid "Number of CPUs. Number is a fractional number. 0.000 means no limit" @@ -1915,7 +1915,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:45 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:111 msgid "Total" -msgstr "" +msgstr "합계" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:170 msgid "Total Memory" diff --git a/luci-app-dockerman/po/lt/dockerman.po b/luci-app-dockerman/po/lt/dockerman.po index 315da8af..0fddcd6a 100644 --- a/luci-app-dockerman/po/lt/dockerman.po +++ b/luci-app-dockerman/po/lt/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-04 18:14+0000\n" +"PO-Revision-Date: 2026-08-27 04:49+0000\n" "Last-Translator: Džiugas Januševičius \n" "Language-Team: Lithuanian \n" @@ -9,7 +9,7 @@ msgstr "" "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" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -62,7 +62,7 @@ msgstr "Pridėtos galimybes" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:634 msgid "Advanced" -msgstr "Pažangūs" +msgstr "Pažangus" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:590 msgid "Aliases" @@ -991,7 +991,7 @@ msgstr "Informacija" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "„Ingress“" +msgstr "Įėjimas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" @@ -1680,7 +1680,7 @@ msgstr "Paleisti – „Run“ komandą" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:84 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:141 msgid "Running" -msgstr "Veikia" +msgstr "Vykdomas/-a" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:833 msgid "Running Processes" @@ -1710,20 +1710,20 @@ msgstr "Pasirinkite tipą pirmą" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:159 msgid "Select image tag" -msgstr "" +msgstr "Pasirinkite laikmenos žyma" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1917 msgid "Select network to connect:" -msgstr "" +msgstr "Pasirinkite tinklą, prie kurio norite prisijungti:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:361 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:474 msgid "Select options..." -msgstr "" +msgstr "Pasirinkite parinktys..." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1150 msgid "Send" -msgstr "" +msgstr "Siųsti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:291 msgid "Set custom DNS servers" @@ -1731,7 +1731,7 @@ msgstr "Nustatyti savuosius „DNS“ serverius" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:301 msgid "Set environment variables inside the container" -msgstr "" +msgstr "Nustatyti aplinkos kintamuosius konteinerio viduje" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:72 msgid "Set the logging level" @@ -1742,6 +1742,8 @@ msgid "" "Set your LAN interface when docker listens on all addresses like 0.0.0.0 " "or ::." msgstr "" +"Nustatykite savo „LAN“ sąsają ir/arba sietuvą, kai „Docker“ laukia " +"prisijungimo/jungties ryšio visų adresų, tokių kaip 0.0.0.0 arba ::." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:423 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:599 @@ -1753,6 +1755,8 @@ msgid "" "Specifies where the Docker daemon will listen for client connections. " "default:" msgstr "" +"Nurodo, kur „Docker“ paslaugų teikimo sistema lauks prisijungimo/jungties " +"kliento ryšių. numatytoji:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1801 @@ -1761,7 +1765,7 @@ msgstr "Paleisti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:208 msgid "Start this container" -msgstr "" +msgstr "Paleisti šį konteinerį" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:161 msgid "State" @@ -1780,15 +1784,15 @@ msgstr "Būklė/Būsena" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1109 msgid "Stderr" -msgstr "" +msgstr "„STDERR“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1101 msgid "Stdin" -msgstr "" +msgstr "“STDIN“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1105 msgid "Stdout" -msgstr "" +msgstr "„STDOUT“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:372 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1803 @@ -1798,11 +1802,11 @@ msgstr "Stabdyti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:155 msgctxt "daemon stop action" msgid "Stop" -msgstr "" +msgstr "Stabdyti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:249 msgid "Stop this container" -msgstr "" +msgstr "Stabdyti šį konteinerį" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:88 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:145 @@ -1811,7 +1815,7 @@ msgstr "Sustabdyta/-s/-i" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1098 msgid "Streams:" -msgstr "" +msgstr "Srautai:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:104 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:103 @@ -1821,24 +1825,24 @@ msgstr "Potinklis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:460 msgid "Subpath" -msgstr "" +msgstr "Sub-kelias" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:81 msgid "Subtype" -msgstr "" +msgstr "Pótipis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1522 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1773 msgid "Success" -msgstr "" +msgstr "Sėkmė" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1012 msgid "Successful" -msgstr "" +msgstr "Sėkmingai" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:542 msgid "Sysctl Settings" -msgstr "" +msgstr "„Sysctl“ nustatymai" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:666 msgid "Sysctl(--sysctl)" @@ -1854,23 +1858,23 @@ msgstr "„TTY“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1763 msgid "TTYd may still be starting up" -msgstr "" +msgstr "„TTYd“ vis dar gali būti paleistas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:635 msgid "Tag" -msgstr "" +msgstr "Žyma" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:670 msgid "Tag added successfully" -msgstr "" +msgstr "Žyma sėkmingai pridėta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:554 msgid "Tag removed successfully" -msgstr "" +msgstr "Žyma sėkmingai pašalinta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:105 msgid "The first match is used." -msgstr "" +msgstr "Pirma atitikmuo yra naudojama." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:641 msgid "The hostname to use for the container" @@ -1879,7 +1883,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:691 msgid "The length of a CPU period in microseconds" -msgstr "" +msgstr "„CPU“ periodo trukmė mikrosekundėmis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:746 msgid "The logging configuration for this container" @@ -1890,12 +1894,16 @@ msgid "" "The user that commands are run as inside the container. (format: name|" "uid[:group|gid])" msgstr "" +"Naudotojas/Vartotojas, kurio komandos yra vykdomos kaip konteinerio viduje. " +"(formatas: name|uid[:group|gid])" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:50 msgid "" "This page displays all docker Containers that have been created on the " "connected docker host." msgstr "" +"Šiame puslapyje rodomi visi „Docker“ konteineriai, kurie buvo sukurti " +"prijungtame „Docker“ skleidėjoje/vedėjoje." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:45 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:58 @@ -1911,6 +1919,8 @@ msgid "" "This page displays all docker volumes that have been created on the " "connected docker host." msgstr "" +"Šiame puslapyje rodomi visi „Docker“ tomai, kurie buvo sukurti prijungtame " +"„Docker“ skleidėjoje/vedėjoje." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Time" @@ -1922,7 +1932,7 @@ msgstr "„Tmpfs“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:528 msgid "Tmpfs Directories" -msgstr "" +msgstr "„Tmpfs“ katalogai/vietovės" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:660 msgid "Tmpfs(--tmpfs)" @@ -1930,15 +1940,15 @@ msgstr "„Tmpfs(--tmpfs)“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:131 msgid "To" -msgstr "" +msgstr "Į" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:138 msgid "Token" -msgstr "" +msgstr "Žetonas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:107 msgid "Tokes and Passwords are not encrypted in the uci configuration." -msgstr "" +msgstr "Žetonai ir slaptažodžiai „UCI“ konfigūracijoje nėra šifruojami." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:82 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:139 @@ -1949,7 +1959,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:45 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:111 msgid "Total" -msgstr "" +msgstr "Iš viso" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:170 msgid "Total Memory" @@ -1960,45 +1970,45 @@ msgstr "Iš viso atminties:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:210 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:220 msgid "Total:" -msgstr "" +msgstr "Iš viso:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:498 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:63 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Type" -msgstr "" +msgstr "Tipas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1135 msgid "Type command here... (Ctrl+D to detach)" -msgstr "" +msgstr "Įveskite komandą čia... („Ctrl+D“, kad atjungtumėte)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "USER" -msgstr "" +msgstr "NAUDOTOJAS/VARTOTOJAS" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:980 msgid "Unexpected error" -msgstr "" +msgstr "Netikėta klaida" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1682 msgid "Unknown error" -msgstr "" +msgstr "Nežinoma klaida" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:485 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:250 msgid "Unless stopped" -msgstr "" +msgstr "Nebent nesustojo" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:396 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1806 msgid "Unpause" -msgstr "" +msgstr "Pratęsti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:208 msgid "Unpause this container" -msgstr "" +msgstr "Pratęsti šį konteinerį" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Update" @@ -2006,7 +2016,7 @@ msgstr "Atnaujinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1358 msgid "Updated" -msgstr "" +msgstr "Atnaujinta/-as" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:428 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:948 @@ -2015,20 +2025,20 @@ msgstr "Įkelti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1609 msgid "Uploading file to container…" -msgstr "" +msgstr "Įkeliamas failas į konteinerį…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:698 msgid "Uploading image…" -msgstr "" +msgstr "Įkeliama laikmena…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1608 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:697 msgid "Uploading…" -msgstr "" +msgstr "Įkeliama…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:103 msgid "Used for push/pull operations on custom registries." -msgstr "" +msgstr "Naudojamas stumti/traukti operacijoms pasirinktiniuose registruose." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:115 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:479 @@ -2046,7 +2056,7 @@ msgstr "„VEPA“ (Virtualaus „ethernet'o“ prievado agregatorius)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "VSZ" -msgstr "" +msgstr "„VSZ“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:803 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:244 @@ -2060,11 +2070,11 @@ msgstr "Versija" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:374 msgid "Volume (named)" -msgstr "" +msgstr "Tomas (pavadintas)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:401 msgid "Volume Name" -msgstr "" +msgstr "Tomo pavadinimas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:219 #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:348 @@ -2082,15 +2092,15 @@ msgstr "Įspėjimas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1078 msgid "WebSocket" -msgstr "" +msgstr "„WebSocket“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1087 msgid "WebSocket Console" -msgstr "" +msgstr "„WebSocket“ konsolė" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1499 msgid "WebSocket error" -msgstr "" +msgstr "„Websocket“ klaida" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:679 msgid "Yes" @@ -2098,35 +2108,35 @@ msgstr "Taip" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:323 msgid "attach" -msgstr "" +msgstr "prijungti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:404 msgid "builder" -msgstr "" +msgstr "„statytojas“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:324 msgid "commit" -msgstr "" +msgstr "nusiųsti/patvirtinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1835 msgid "completed" -msgstr "" +msgstr "užbaigta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:405 msgid "config" -msgstr "" +msgstr "konfigūracija" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:372 msgid "connect" -msgstr "" +msgstr "prisijungti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:406 msgid "container" -msgstr "" +msgstr "konteineris" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:325 msgid "copy" -msgstr "" +msgstr "kopijuoti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:326 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:357 @@ -2134,109 +2144,111 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:382 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:391 msgid "create" -msgstr "" +msgstr "sukurti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:407 msgid "daemon" -msgstr "" +msgstr "paslaugų teikimo sistema" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:358 msgid "delete" -msgstr "" +msgstr "ištrinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:327 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:374 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:385 msgid "destroy" -msgstr "" +msgstr "sunaikinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:328 msgid "detach" -msgstr "" +msgstr "atjungti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:329 msgid "die" -msgstr "" +msgstr "(už)baigti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:373 msgid "disconnect" -msgstr "" +msgstr "atsijungti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:330 msgid "exec_create" -msgstr "" +msgstr "„exec_create“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:331 msgid "exec_detach" -msgstr "" +msgstr "„exec_detach“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:333 msgid "exec_die" -msgstr "" +msgstr "„exec_die“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:332 msgid "exec_start" -msgstr "" +msgstr "„exec_start“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:334 msgid "export" -msgstr "" +msgstr "eksportuoti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:206 msgid "git repositories require git installed on the docker host." msgstr "" +"„Git“ saugykloms reikalinga, kad „Git“ būtų įdiegta „Docker“ skleidėjoje/" +"vedėjoje." #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:335 msgid "health_status" -msgstr "" +msgstr "„health_status“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:408 msgid "image" -msgstr "" +msgstr "laikmena" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:755 msgid "json-file" -msgstr "" +msgstr "„json-file“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:336 msgid "kill" -msgstr "" +msgstr "„nužudyti“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:360 msgid "load" -msgstr "" +msgstr "įkelti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:754 msgid "local" -msgstr "" +msgstr "vietinis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:383 msgid "mount" -msgstr "" +msgstr "įrengti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:409 msgid "network" -msgstr "" +msgstr "tinklas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:410 msgid "node" -msgstr "" +msgstr "tinklo mazgas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:757 msgid "none" -msgstr "" +msgstr "joks (-ia/-ie/-ios)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:337 msgid "oom" -msgstr "" +msgstr "„oom“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:338 msgid "pause" -msgstr "" +msgstr "pristabdyti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:411 msgid "plugin" -msgstr "" +msgstr "plėtinys" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:318 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:347 @@ -2244,96 +2256,96 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:377 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:386 msgid "prune" -msgstr "" +msgstr "išvalyti nenaudojamus" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:869 msgid "ps flags:" -msgstr "" +msgstr "„ps“ žymos:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:352 msgid "reload" -msgstr "" +msgstr "perleisti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:376 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:393 msgid "remove" -msgstr "" +msgstr "pašalinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:339 msgid "rename" -msgstr "" +msgstr "pervadinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:340 msgid "resize" -msgstr "" +msgstr "keisti dydį" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:341 msgid "restart" -msgstr "" +msgstr "paleisti iš naujo" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:363 msgid "save" -msgstr "" +msgstr "išsaugoti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:412 msgid "secret" -msgstr "" +msgstr "paslaptis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:413 msgid "service" -msgstr "" +msgstr "tarnyba" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:342 msgid "start" -msgstr "" +msgstr "paleisti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:343 msgid "stop" -msgstr "" +msgstr "stabdyti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:756 msgid "syslog" -msgstr "" +msgstr "„syslog“" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:364 msgid "tag" -msgstr "" +msgstr "žyma" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:437 msgid "tmpfs Options" -msgstr "" +msgstr "„tmpfs“ parinktys" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:344 msgid "top" -msgstr "" +msgstr "viršus" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:384 msgid "unmount" -msgstr "" +msgstr "išrengti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:345 msgid "unpause" -msgstr "" +msgstr "pratęsti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:365 msgid "untag" -msgstr "" +msgstr "atžymėti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:346 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:375 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:392 msgid "update" -msgstr "" +msgstr "atnaujinti" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:414 msgid "volume" -msgstr "" +msgstr "tomas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:126 msgid "volume name" -msgstr "" +msgstr "tomo pavadinimas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1360 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:991 msgid "with warnings" -msgstr "" +msgstr "su įspėjimais" diff --git a/luci-app-dockerman/po/pt_BR/dockerman.po b/luci-app-dockerman/po/pt_BR/dockerman.po index 1a00c9f4..bc674b35 100644 --- a/luci-app-dockerman/po/pt_BR/dockerman.po +++ b/luci-app-dockerman/po/pt_BR/dockerman.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-05-18 07:49+0000\n" -"Last-Translator: Volenski \n" +"PO-Revision-Date: 2026-09-02 07:50+0000\n" +"Last-Translator: Martin Olmer \n" "Language-Team: Portuguese (Brazil) \n" "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" -"X-Generator: Weblate 2026.6.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -61,7 +61,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:634 msgid "Advanced" -msgstr "" +msgstr "Avançada" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:590 msgid "Aliases" @@ -324,7 +324,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1520 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1532 msgid "Connected" -msgstr "" +msgstr "Conectado" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1063 msgid "Connected to container console" @@ -436,7 +436,7 @@ msgstr "Visão geral dos contêineres" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:421 msgid "Create" -msgstr "" +msgstr "Criar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:979 msgid "Create container" diff --git a/luci-app-dockerman/po/ru/dockerman.po b/luci-app-dockerman/po/ru/dockerman.po index 303d1408..730bb14b 100644 --- a/luci-app-dockerman/po/ru/dockerman.po +++ b/luci-app-dockerman/po/ru/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-08-14 12:12+0000\n" +"PO-Revision-Date: 2026-09-01 09:13+0000\n" "Last-Translator: SnIPeRSnIPeR " "\n" "Language-Team: Russian \n" +"PO-Revision-Date: 2026-08-23 19:31+0000\n" +"Last-Translator: Pavel Borecki \n" "Language-Team: Tamil \n" "Language: ta\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.12-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -861,7 +861,7 @@ msgstr "புரவலன் பெயர்" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:60 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:178 msgid "ID" -msgstr "ஐடி" +msgstr "ID" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:45 msgid "IP VLAN" diff --git a/luci-app-dockerman/po/tr/dockerman.po b/luci-app-dockerman/po/tr/dockerman.po index 4c48013e..c36a2789 100644 --- a/luci-app-dockerman/po/tr/dockerman.po +++ b/luci-app-dockerman/po/tr/dockerman.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-07-28 20:29+0000\n" -"Last-Translator: Oğuz Ersen \n" +"PO-Revision-Date: 2026-08-31 21:03+0000\n" +"Last-Translator: Martin Olmer \n" "Language-Team: Turkish \n" "Language: tr\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2026.8.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -61,7 +61,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:634 msgid "Advanced" -msgstr "" +msgstr "Gelişmiş" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:590 msgid "Aliases" @@ -482,7 +482,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:73 msgid "Debug" -msgstr "Hata Ayıklama" +msgstr "Hata ayıklama" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:753 msgid "Default (daemon)" @@ -1660,7 +1660,7 @@ msgstr "Komut çalıştır" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:84 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:141 msgid "Running" -msgstr "" +msgstr "Çalışıyor" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:833 msgid "Running Processes" @@ -1745,7 +1745,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:161 msgid "State" -msgstr "" +msgstr "Durum" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:703 msgid "Stats" @@ -1773,7 +1773,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:372 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1803 msgid "Stop" -msgstr "Dur" +msgstr "Durdur" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:155 msgctxt "daemon stop action" @@ -1787,7 +1787,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:88 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:145 msgid "Stopped" -msgstr "" +msgstr "Durduruldu" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1098 msgid "Streams:" @@ -1893,7 +1893,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Time" -msgstr "" +msgstr "Zaman" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:376 msgid "Tmpfs" @@ -1945,7 +1945,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:63 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Type" -msgstr "" +msgstr "Tür" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1135 msgid "Type command here... (Ctrl+D to detach)" @@ -1963,7 +1963,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1682 msgid "Unknown error" -msgstr "" +msgstr "Bilinmeyen hata" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:485 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:250 @@ -1990,7 +1990,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:428 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:948 msgid "Upload" -msgstr "Yükleme" +msgstr "Yükle" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1609 msgid "Uploading file to container…" @@ -2035,7 +2035,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:240 msgid "Version" -msgstr "" +msgstr "Sürüm" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:374 msgid "Volume (named)" diff --git a/luci-app-dockerman/po/zh_Hans/dockerman.po b/luci-app-dockerman/po/zh_Hans/dockerman.po index 2b2711fc..b3364891 100644 --- a/luci-app-dockerman/po/zh_Hans/dockerman.po +++ b/luci-app-dockerman/po/zh_Hans/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-08-15 13:57+0000\n" +"PO-Revision-Date: 2026-08-31 06:44+0000\n" "Last-Translator: Hosted Weblate user 54392 " "\n" "Language-Team: Chinese (Simplified Han script) \n" +"PO-Revision-Date: 2026-08-17 17:58+0000\n" +"Last-Translator: 加尼瑪空格把變數都破壞了! " +"\n" "Language-Team: Chinese (Traditional Han script) \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" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" diff --git a/luci-app-ksmbd/po/es/ksmbd.po b/luci-app-ksmbd/po/es/ksmbd.po index 9289db84..77e4f2bc 100644 --- a/luci-app-ksmbd/po/es/ksmbd.po +++ b/luci-app-ksmbd/po/es/ksmbd.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2019-10-16 16:58-0300\n" -"PO-Revision-Date: 2026-02-27 01:01+0000\n" +"PO-Revision-Date: 2026-08-24 16:48+0000\n" "Last-Translator: Franco Castillo \n" "Language-Team: Spanish \n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.16.1-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" @@ -73,7 +73,7 @@ msgstr "Forzar Root" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:33 msgid "General Settings" -msgstr "Ajustes generales" +msgstr "Configuración general" #: applications/luci-app-ksmbd/root/usr/share/rpcd/acl.d/luci-app-ksmbd.json:3 msgid "Grant access to LuCI app ksmbd" diff --git a/luci-app-ksmbd/po/lt/ksmbd.po b/luci-app-ksmbd/po/lt/ksmbd.po index 8fbae65a..c47a3dd8 100644 --- a/luci-app-ksmbd/po/lt/ksmbd.po +++ b/luci-app-ksmbd/po/lt/ksmbd.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"PO-Revision-Date: 2025-12-30 17:00+0000\n" +"PO-Revision-Date: 2026-08-27 11:44+0000\n" "Last-Translator: Džiugas Januševičius \n" "Language-Team: Lithuanian \n" @@ -11,7 +11,7 @@ msgstr "" "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" -"X-Generator: Weblate 5.15.1\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" diff --git a/luci-app-ksmbd/po/ru/ksmbd.po b/luci-app-ksmbd/po/ru/ksmbd.po index 7423722f..f23c5a7e 100644 --- a/luci-app-ksmbd/po/ru/ksmbd.po +++ b/luci-app-ksmbd/po/ru/ksmbd.po @@ -1,7 +1,8 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-07-03 15:29+0000\n" -"Last-Translator: chigelo \n" +"PO-Revision-Date: 2026-08-28 06:20+0000\n" +"Last-Translator: SnIPeRSnIPeR " +"\n" "Language-Team: Russian \n" "Language: ru\n" @@ -9,7 +10,7 @@ msgstr "" "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" -"X-Generator: Weblate 2026.7.1.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" diff --git a/luci-app-ksmbd/po/zh_Hans/ksmbd.po b/luci-app-ksmbd/po/zh_Hans/ksmbd.po index 359e2a34..e5519896 100644 --- a/luci-app-ksmbd/po/zh_Hans/ksmbd.po +++ b/luci-app-ksmbd/po/zh_Hans/ksmbd.po @@ -2,8 +2,9 @@ msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2026-04-04 14:23+0000\n" -"Last-Translator: evan-yang \n" +"PO-Revision-Date: 2026-08-31 06:44+0000\n" +"Last-Translator: Hosted Weblate user 54392 " +"\n" "Language-Team: Chinese (Simplified Han script) \n" "Language: zh_Hans\n" @@ -11,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.17-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" @@ -69,7 +70,7 @@ msgstr "强制 Root" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:33 msgid "General Settings" -msgstr "常规设置" +msgstr "通用设置" #: applications/luci-app-ksmbd/root/usr/share/rpcd/acl.d/luci-app-ksmbd.json:3 msgid "Grant access to LuCI app ksmbd" diff --git a/luci-app-openclaw/CHANGELOG.md b/luci-app-openclaw/CHANGELOG.md index 56335fb9..8273b610 100644 --- a/luci-app-openclaw/CHANGELOG.md +++ b/luci-app-openclaw/CHANGELOG.md @@ -4,6 +4,38 @@ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 +## [2.2.0] - 2026-09-06 + +本次发布全面适配 OpenClaw 2026.9.1 最新稳定版基线,完成安全升级事务、外部 Supervisor 治理、微信插件 2.4.8 与废弃特性清理。 + +### 新增与优化 + +- **OpenClaw 2026.9.1 基线适配**:默认 OpenClaw 升级至 `2026.9.1`,Node.js 运行时基线升级至 `22.23.2`,微信插件升级至 `@tencent-weixin/openclaw-weixin@2.4.8`(兼容 openclaw >=2026.5.12)。 +- **安全升级事务机(Staging & Explicit Rollback)**: + - 杜绝原地破坏性 npm 覆写,升级采用独立 Staging 暂存目录提取与包契约校验(`openclaw-package-contract.js`),支持 npm lifecycle 白名单过滤。 + - 新增 `openclaw-upgrade-state.sh`,在 `.luci-openclaw-upgrade/status.json` 中完整记录升级事务状态(`phase`、`target_version`、`backup_verified`、`migration_started`、`rollback_mode`、`error_code`)。 + - 确立安全红线:迁移一旦开始,任何失败均进入 `recovery` 状态并保留 2026.9.1 候选与迁移状态,严禁旧代码自动覆写已迁移数据库,必须通过 `rollback-explicit` 进行受控的显式回滚。 +- **外部 Supervisor 治理**: + - 在 procd 守护进程、LuCI 运行时以及 CLI 执行环境中全面注入 `OPENCLAW_SUPERVISOR_MODE="external"` 与 `OPENCLAW_SERVICE_REPAIR_POLICY="external"`,禁止上游内部 supervisor 干预 OpenWrt procd 的进程与自愈生命周期。 +- **废弃补丁与弃用配置清理**: + - 彻底移除对旧版本 upstream dist 的历史猴子补丁(`patch_iframe_headers`、`patch_webchat_session_conflict`)。 + - 彻底移除上游已废弃的配置项 `gateway.controlUi.dangerouslyDisableDeviceAuth` 及 `d.plugins.installs` 写入,全量契约测试均已锁定。 +- **微信插件运行时合约验证**: + - 微信插件升级至 `2.4.8`,废弃脆弱的纯文本正则匹配,改用官方 JSON 合约 `plugins inspect openclaw-weixin --runtime --json` 与 `plugins registry --refresh --json` 验证渠道健康度。 +- **前端体验与低负载轮询优化**: + - LuCI 状态页面轮询增加 `document.hidden` 检查与 `visibilitychange` 事件监听,后台标签页自动挂起轮询,切换回前台时即时触发刷新,降低 OpenWrt 路由器 CPU 负载。 + - 安装弹窗明确标注 `v2026.9.1 (已验证版)` 与 `最新版 (未经验证)`,并在 openclaw-env 未就绪时优雅兜底。 +- **Web 控制台原生会话内嵌与自动探活**: + - Web 控制台页面原生内嵌 OpenClaw 官方 WebChat / Control UI 会话 iframe,携带 Gateway 鉴权令牌自动免密登录,支持一键全屏切换与会话就地刷新; + - 增加状态探活轮询机制,网关启动中时自动平滑重试并完成内嵌载入,无需手动 F5 刷新;增加 HTTPS 混杂内容安全指引。 +- **一键设备配对管理(Web 端与终端配置)**: + - 全面支持 OpenClaw 2026.9.1 新增的设备配对安全机制,在 Web 控制台与终端配置页增加待配对请求悬浮检测卡片与一键批准功能; + - 交互式终端与 Shell 菜单新增 `[p] 设备配对管理 / 一键批准设备配对`,支持批量或单独批准,免除手动复制复杂 ID 的门槛; + - 在 Web 界面与终端各处醒目标注安全风险提示,且控制器使用 `post()` + CSRF 保护,对客户端元数据做 HTML 转义杜绝存储型 XSS。 +- **配置管理模型菜单优化与终端重连加固**: + - 移除模型提供商顶级菜单中硬编码的具体过时模型名称,具体型号选择移交二级向导动态发现与精选预设; + - 修复终端配置点击重启后因连接代际竞争导致的“等待服务就绪... 5秒后重试”无限循环问题,引入序列锁与 Cookie 凭据注入,实现秒级就地重连。 + ## [2.1.1] - 2026-08-27 本次维护以 OpenClaw 2026.7.1-2 为基准,逐项核对配置写入与上游 schema 的一致性。 diff --git a/luci-app-openclaw/Makefile b/luci-app-openclaw/Makefile index 3b287070..ae2d3c38 100644 --- a/luci-app-openclaw/Makefile +++ b/luci-app-openclaw/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-openclaw PKG_VERSION:=$(strip $(shell cat $(CURDIR)/VERSION 2>/dev/null || echo "1.0.0")) -PKG_RELEASE:=26 +PKG_RELEASE:=27 PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com> PKG_LICENSE:=GPL-3.0 @@ -67,6 +67,7 @@ define Package/$(PKG_NAME)/install $(INSTALL_BIN) ./root/usr/libexec/openclaw-paths.sh $(1)/usr/libexec/openclaw-paths.sh $(INSTALL_BIN) ./root/usr/libexec/openclaw-node.sh $(1)/usr/libexec/openclaw-node.sh $(INSTALL_BIN) ./root/usr/libexec/openclaw-permissions.sh $(1)/usr/libexec/openclaw-permissions.sh + $(INSTALL_BIN) ./root/usr/libexec/openclaw-upgrade-state.sh $(1)/usr/libexec/openclaw-upgrade-state.sh $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller $(INSTALL_DATA) ./luasrc/controller/openclaw.lua $(1)/usr/lib/lua/luci/controller/openclaw.lua $(INSTALL_DIR) $(1)/usr/lib/lua/openclaw @@ -86,6 +87,7 @@ define Package/$(PKG_NAME)/install $(INSTALL_DATA) ./root/usr/share/openclaw/web-pty.js $(1)/usr/share/openclaw/web-pty.js $(INSTALL_BIN) ./root/usr/share/openclaw/oc-config-interactive.js $(1)/usr/share/openclaw/oc-config-interactive.js $(INSTALL_DATA) ./root/usr/share/openclaw/oc-menu-engine.js $(1)/usr/share/openclaw/oc-menu-engine.js + $(INSTALL_BIN) ./root/usr/share/openclaw/openclaw-package-contract.js $(1)/usr/share/openclaw/openclaw-package-contract.js $(INSTALL_DATA) ./root/usr/share/openclaw/model-presets.json $(1)/usr/share/openclaw/model-presets.json $(INSTALL_DIR) $(1)/usr/share/openclaw/ui $(CP) ./root/usr/share/openclaw/ui/* $(1)/usr/share/openclaw/ui/ diff --git a/luci-app-openclaw/README.md b/luci-app-openclaw/README.md index 430b7b31..900aefd8 100644 --- a/luci-app-openclaw/README.md +++ b/luci-app-openclaw/README.md @@ -27,9 +27,14 @@ | 组件 | 默认版本 | 说明 | |------|----------|------| -| OpenClaw | `2026.7.1-2` | npm `latest` 稳定标签 | -| Node.js | `22.23.0` | Alpine musl LTS 包;OpenClaw 2026.7.x 要求 `>=22.22.3`,安装后会按 `engines.node` 做强校验 | -| 微信插件 | `@tencent-weixin/openclaw-weixin@2.4.6` | CLI 使用 `@tencent-weixin/openclaw-weixin-cli@2.1.4` | +| OpenClaw | `2026.9.1` | npm `latest` 稳定标签(Phase 0 锁定已测试版本;安装时阻断未知脚本,仅运行已校验 lifecycle) | +| Node.js | `22.23.2` | x86_64 与 aarch64 均优先从 unofficial-builds 及镜像源获取,自托管资产作为兜底;严格验证 `engines.node`,失败关闭 | +| 微信插件 | `@tencent-weixin/openclaw-weixin@2.4.8` | CLI 使用 `@tencent-weixin/openclaw-weixin-cli@2.1.4` | + +### 运行时与架构说明 + +- **x86_64 / aarch64 (musl)**:Node.js v22.23.2 预编译运行时优先通过上游 [unofficial-builds](https://unofficial-builds.nodejs.org/) 及 npmmirror 镜像源获取,安装时自动下载并强校验 `SHASUMS256.txt`。同时在 `.github/workflows/build-node-musl.yml` 中保留基于 Alpine LTS 的自托管构建打包流程作为兜底源。 + ## 📦 安装 diff --git a/luci-app-openclaw/VERSION b/luci-app-openclaw/VERSION index 3e3c2f1e..ccbccc3d 100644 --- a/luci-app-openclaw/VERSION +++ b/luci-app-openclaw/VERSION @@ -1 +1 @@ -2.1.1 +2.2.0 diff --git a/luci-app-openclaw/luasrc/controller/openclaw.lua b/luci-app-openclaw/luasrc/controller/openclaw.lua index d139638a..ccf23e65 100644 --- a/luci-app-openclaw/luasrc/controller/openclaw.lua +++ b/luci-app-openclaw/luasrc/controller/openclaw.lua @@ -209,7 +209,7 @@ local function openclaw_user_runner_cmd() "_oc_raise_openclaw_limits; " .. "if command -v su >/dev/null 2>&1; then su -s /bin/sh openclaw -c \"$1\"; " .. "elif command -v runuser >/dev/null 2>&1; then runuser -u openclaw -- sh -c \"$1\"; " .. - "elif command -v start-stop-daemon >/dev/null 2>&1; then _oc_pid=/tmp/openclaw-user-$$.pid; _oc_cwd=$(pwd); rm -f \"$_oc_pid\"; start-stop-daemon -S -m -p \"$_oc_pid\" -c openclaw:openclaw -d \"$_oc_cwd\" -x /bin/sh -- -c \"$1\"; _oc_rc=$?; rm -f \"$_oc_pid\"; return $_oc_rc; " .. + "elif command -v start-stop-daemon >/dev/null 2>&1; then _oc_pid=/tmp/openclaw-user-$$.pid; _oc_cwd=$(pwd); rm -f \"$_oc_pid\"; (cd \"$_oc_cwd\" 2>/dev/null || cd /tmp; start-stop-daemon -S -m -p \"$_oc_pid\" -c openclaw:openclaw -x /bin/sh -- -c \"$1\"); _oc_rc=$?; rm -f \"$_oc_pid\"; return $_oc_rc; " .. "else echo '❌ 缺少 su/runuser/start-stop-daemon,无法以 openclaw 用户运行命令' >&2; return 127; fi; " .. "}; " end @@ -219,9 +219,10 @@ local function wechat_openclaw_plugin_install_cmd(install_path, oc_entry, log_fi "OC_WECHAT_ENTRY=" .. shellquote(oc_entry) .. "; export OC_WECHAT_NODE OC_WECHAT_ENTRY; " .. "echo '使用 OpenClaw 官方插件安装器写入 SQLite 插件索引...' >> " .. shellquote(log_file) .. "; " .. "_oc_as_openclaw 'HOME=$OC_WECHAT_DATA OPENCLAW_HOME=$OC_WECHAT_DATA OPENCLAW_STATE_DIR=$OC_WECHAT_DATA/.openclaw OPENCLAW_CONFIG_PATH=$OC_WECHAT_DATA/.openclaw/openclaw.json " .. + "OPENCLAW_SUPERVISOR_MODE=external OPENCLAW_SERVICE_REPAIR_POLICY=external " .. "NODE_ICU_DATA=" .. install_path .. "/node/share/icu NPM_CONFIG_CACHE=$OC_WECHAT_DATA/.npm npm_config_cache=$OC_WECHAT_DATA/.npm TMPDIR=$OC_WECHAT_DATA/.tmp " .. "PATH=" .. install_path .. "/node/bin:" .. install_path .. "/global/bin:$PATH " .. - "\"$OC_WECHAT_NODE\" \"$OC_WECHAT_ENTRY\" plugins install --force --pin @tencent-weixin/openclaw-weixin@2.4.6' >> " .. shellquote(log_file) .. " 2>&1; " .. + "\"$OC_WECHAT_NODE\" \"$OC_WECHAT_ENTRY\" plugins install --force --pin @tencent-weixin/openclaw-weixin@2.4.8' >> " .. shellquote(log_file) .. " 2>&1; " .. "RC=$?; echo $RC > " .. shellquote(exit_file) .. "; " .. "if [ $RC -eq 0 ]; then echo '✅ OpenClaw 插件索引注册完成' >> " .. shellquote(log_file) .. "; else echo '❌ OpenClaw 插件安装失败 (exit: '$RC')' >> " .. shellquote(log_file) .. "; fi; " end @@ -233,6 +234,7 @@ local function wechat_finalize_plugin_registry_cmd(install_path, oc_entry, log_f local cli_env = "HOME=" .. oc_data .. " OPENCLAW_HOME=" .. oc_data .. " OPENCLAW_STATE_DIR=" .. oc_data .. "/.openclaw" .. " OPENCLAW_CONFIG_PATH=" .. oc_data .. "/.openclaw/openclaw.json" .. + " OPENCLAW_SUPERVISOR_MODE=external OPENCLAW_SERVICE_REPAIR_POLICY=external" .. " NODE_ICU_DATA=" .. install_path .. "/node/share/icu" .. " NPM_CONFIG_CACHE=" .. oc_data .. "/.npm TMPDIR=" .. oc_data .. "/.tmp" .. " PATH=" .. install_path .. "/node/bin:" .. install_path .. "/global/bin:$PATH " @@ -244,13 +246,14 @@ local function wechat_finalize_plugin_registry_cmd(install_path, oc_entry, log_f "FINAL_RC=$?; " .. "if [ $FINAL_RC -eq 0 ]; then " .. "echo '正在刷新 SQLite 插件注册表...' >> " .. shellquote(log_file) .. "; " .. - "_oc_as_openclaw '" .. cli .. " plugins registry --refresh' >> " .. shellquote(log_file) .. " 2>&1; " .. + "_oc_as_openclaw '" .. cli .. " plugins registry --refresh --json' >> " .. shellquote(log_file) .. " 2>&1; " .. "FINAL_RC=$?; fi; " .. "if [ $FINAL_RC -eq 0 ]; then " .. + "echo '正在验证微信插件运行时状态 (plugins inspect openclaw-weixin --runtime --json)...' >> " .. shellquote(log_file) .. "; " .. "_OC_WECHAT_INSPECT=/tmp/openclaw-wechat-inspect-$$.log; " .. - "_oc_as_openclaw '" .. cli .. " plugins inspect openclaw-weixin' > \"$_OC_WECHAT_INSPECT\" 2>&1; " .. + "_oc_as_openclaw '" .. cli .. " plugins inspect openclaw-weixin --runtime --json' > \"$_OC_WECHAT_INSPECT\" 2>&1; " .. "VERIFY_RC=$?; cat \"$_OC_WECHAT_INSPECT\" >> " .. shellquote(log_file) .. "; " .. - "if [ $VERIFY_RC -ne 0 ] || ! grep -q '^Status: loaded$' \"$_OC_WECHAT_INSPECT\" || ! grep -q '^channel: openclaw-weixin$' \"$_OC_WECHAT_INSPECT\"; then FINAL_RC=1; fi; " .. + "if [ $VERIFY_RC -ne 0 ] || ! " .. node_bin .. " -e 'const fs=require(\"fs\"); const raw=fs.readFileSync(process.argv[1],\"utf8\"); try { const j=JSON.parse(raw); const ok=(j && (j.status===\"loaded\" || j.loaded===true || j.enabled===true || j.channel===\"openclaw-weixin\" || (j.channels && j.channels.includes(\"openclaw-weixin\")) || (j.capabilities && j.capabilities.includes(\"channel\")))); process.exit(ok ? 0 : 1); } catch(e) { process.exit(1); }' \"$_OC_WECHAT_INSPECT\" >/dev/null 2>&1; then FINAL_RC=1; fi; " .. "rm -f \"$_OC_WECHAT_INSPECT\"; fi; " .. "if [ $FINAL_RC -eq 0 ]; then " .. "echo '✅ 微信插件已启用,SQLite 注册表已刷新并通过加载验证' >> " .. shellquote(log_file) .. "; " .. @@ -382,6 +385,10 @@ function index() -- 微信退出/删除账号 API entry({"admin", "services", "openclaw", "wechat_logout"}, post("action_wechat_logout"), nil).leaf = true + + -- 设备配对管理 API (v2026.9.1+ 浏览器设备配对审批) + entry({"admin", "services", "openclaw", "devices_list"}, call("action_devices_list"), nil).leaf = true + entry({"admin", "services", "openclaw", "devices_approve"}, post("action_devices_approve"), nil).leaf = true end-- ═══════════════════════════════════════════ -- 获取安装路径 (唯一权威来源: UCI 配置) -- ═══════════════════════════════════════════ @@ -448,6 +455,49 @@ local function ensure_port_free(port) end end +local function read_upgrade_transaction_status(install_path) + local status = { + phase = "idle", + target_version = "", + backup_verified = false, + migration_started = false, + rollback_mode = "none", + error_code = 0 + } + install_path = install_path or get_install_path() + local candidates = { + install_path .. "/.luci-openclaw-upgrade/status.json", + install_path .. "/data/.luci-openclaw-upgrade/status.json", + install_path .. "/data/.openclaw/.luci-openclaw-upgrade/status.json", + "/tmp/.luci-openclaw-upgrade/status.json" + } + for _, fpath in ipairs(candidates) do + local f = io.open(fpath, "r") + if f then + local content = f:read("*a") + f:close() + if content and content ~= "" then + local phase = content:match('"phase"%s*:%s*"([^"]+)"') + local target_ver = content:match('"target_version"%s*:%s*"([^"]*)"') + local bk_ver = content:match('"backup_verified"%s*:%s*([%a]+)') + local mig_st = content:match('"migration_started"%s*:%s*([%a]+)') + local rb_mode = content:match('"rollback_mode"%s*:%s*"([^"]+)"') + local err_code = content:match('"error_code"%s*:%s*"?(-?%d+)"?') + if phase then status.phase = phase end + if target_ver then status.target_version = target_ver end + if bk_ver == "true" then status.backup_verified = true + elseif bk_ver == "false" then status.backup_verified = false end + if mig_st == "true" then status.migration_started = true + elseif mig_st == "false" then status.migration_started = false end + if rb_mode then status.rollback_mode = rb_mode end + if err_code then status.error_code = tonumber(err_code) or 0 end + break + end + end + end + return status +end + -- ═══════════════════════════════════════════ -- 状态查询 API: 返回 JSON -- ═══════════════════════════════════════════ @@ -484,6 +534,12 @@ function action_status() oc_version = "", plugin_version = "", disk_free = "", + phase = "idle", + target_version = "", + backup_verified = false, + migration_started = false, + rollback_mode = "none", + error_code = 0, } -- 插件版本 @@ -636,6 +692,14 @@ function action_status() result.disk_free = df_output end + local up_status = read_upgrade_transaction_status(install_path) + result.phase = up_status.phase + result.target_version = up_status.target_version + result.backup_verified = up_status.backup_verified + result.migration_started = up_status.migration_started + result.rollback_mode = up_status.rollback_mode + result.error_code = up_status.error_code + http.prepare_content("application/json") http.write_json(result) end @@ -708,6 +772,23 @@ function action_service_ctl() http.prepare_content("application/json") http.write_json({ status = "ok", message = "安装已启动,请查看安装日志..." }) return + elseif action == "upgrade" then + -- 先清理旧日志和状态 + sys.exec("rm -f /tmp/openclaw-setup.log /tmp/openclaw-setup.pid /tmp/openclaw-setup.exit") + local target_ver = http.formvalue("version") or "" + local env_prefix = "" + if target_ver == "latest" then + env_prefix = "OC_VERSION='latest' " + elseif target_ver ~= "" and target_ver ~= "stable" then + if target_ver:match("^[%d%.%-a-zA-Z]+$") then + env_prefix = "OC_VERSION=" .. shellquote(target_ver) .. " " + end + end + -- 异步执行 openclaw-env upgrade 事务状态机 + sys.exec("( " .. env_prefix .. "/usr/bin/openclaw-env upgrade > /tmp/openclaw-setup.log 2>&1; echo $? > /tmp/openclaw-setup.exit ) & echo $! > /tmp/openclaw-setup.pid") + http.prepare_content("application/json") + http.write_json({ status = "ok", message = "OpenClaw 核心升级已启动,正在执行安全事务..." }) + return else http.prepare_content("application/json") http.write_json({ status = "error", message = "未知操作: " .. action }) @@ -766,11 +847,18 @@ function action_setup_log() state = "failed" end + local up_status = read_upgrade_transaction_status() http.prepare_content("application/json") http.write_json({ state = state, exit_code = exit_code, - log = log + log = log, + phase = up_status.phase, + target_version = up_status.target_version, + backup_verified = up_status.backup_verified, + migration_started = up_status.migration_started, + rollback_mode = up_status.rollback_mode, + error_code = up_status.error_code }) end @@ -816,13 +904,47 @@ function action_check_update() plugin_has_update = true end + -- 2. OpenClaw 核心版本检查 + local openclaw_current = "" + local openclaw_latest = "" + local openclaw_has_update = false + + local oc_check_str = sys.exec("/usr/bin/openclaw-env check 2>/dev/null | grep -i 'OpenClaw:' | head -1"):gsub("%s+", "") + if oc_check_str ~= "" then + openclaw_current = oc_check_str:match("[vV]?([%d%.%-]+)") or "" + end + if openclaw_current == "" or openclaw_current == "未安装" then + -- 备用路径读取: 从 package.json 探测 + local candidate_pkg = "/opt/openclaw/global/lib/node_modules/openclaw/package.json" + local f_pkg = io.open(candidate_pkg, "r") + if f_pkg then + local content = f_pkg:read("*a") + f_pkg:close() + openclaw_current = content:match('"version"%s*:%s*"([^"]+)"') or "" + end + end + + -- 推荐/已验证的最新稳定版本 + local tested_ver = sys.exec("sed -n 's/^OC_TESTED_VERSION=\"\\(.*\\)\"/\\1/p' /usr/bin/openclaw-env 2>/dev/null"):gsub("%s+", "") + if tested_ver == "" then tested_ver = "2026.9.1" end + openclaw_latest = tested_ver + + if openclaw_current ~= "" and openclaw_current ~= "未安装" and openclaw_latest ~= "" then + if is_newer_version(openclaw_latest, openclaw_current) then + openclaw_has_update = true + end + end + http.prepare_content("application/json") http.write_json({ status = "ok", plugin_current = plugin_current, plugin_latest = plugin_latest, plugin_has_update = plugin_has_update, - release_notes = release_notes + release_notes = release_notes, + openclaw_current = openclaw_current, + openclaw_latest = openclaw_latest, + openclaw_has_update = openclaw_has_update }) end @@ -900,6 +1022,16 @@ function action_get_token() local uci = require "luci.model.uci".cursor() local token = uci:get("openclaw", "main", "token") or "" local pty_token = uci:get("openclaw", "main", "pty_token") or "" + if token == "" then + local install_path = normalize_install_path(uci:get("openclaw", "main", "install_path")) + local config_file = install_path .. "/data/.openclaw/openclaw.json" + local cf = io.open(config_file, "r") + if cf then + local content = cf:read("*a") + cf:close() + token = content:match('"token"%s*:%s*"([^"]+)"') or "" + end + end http.prepare_content("application/json") http.write_json({ token = token, pty_token = pty_token }) end @@ -1599,7 +1731,7 @@ function action_wechat_install() wechat_python3_bootstrap_cmd("/tmp/openclaw-wechat-install.log") .. "OC_WECHAT_DATA=%s; export OC_WECHAT_DATA; " .. "if [ -x /usr/libexec/openclaw-permissions.sh ]; then /usr/libexec/openclaw-permissions.sh prepare-workdirs \"$OC_WECHAT_DATA\" >/dev/null 2>&1; " .. - "else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " .. + "else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw/extensions\" 2>/dev/null; chmod -R 755 \"$OC_WECHAT_DATA/.openclaw/extensions\" 2>/dev/null; fi; " .. "if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " .. "_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " .. wechat_openclaw_plugin_install_cmd(install_path, oc_entry, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") .. @@ -2114,7 +2246,7 @@ function action_wechat_upgrade_plugin() wechat_python3_bootstrap_cmd("/tmp/openclaw-wechat-install.log") .. "OC_WECHAT_DATA=%s; export OC_WECHAT_DATA; " .. "if [ -x /usr/libexec/openclaw-permissions.sh ]; then /usr/libexec/openclaw-permissions.sh prepare-workdirs \"$OC_WECHAT_DATA\" >/dev/null 2>&1; " .. - "else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " .. + "else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw/extensions\" 2>/dev/null; chmod -R 755 \"$OC_WECHAT_DATA/.openclaw/extensions\" 2>/dev/null; fi; " .. "if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " .. "_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " .. wechat_openclaw_plugin_install_cmd(install_path, oc_entry, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") .. @@ -2205,3 +2337,165 @@ function action_wechat_logout() http.prepare_content("application/json") http.write_json({ status = "ok", message = "已下线账号: " .. account_id }) end + +-- ═══════════════════════════════════════════ +-- 设备配对管理 API (v2026.9.1+ Control UI 设备配对审批) +-- ═══════════════════════════════════════════ +local function run_openclaw_devices_cli(args) + local sys = require "luci.sys" + local install_path = get_install_path() + local node_bin = install_path .. "/node/bin/node" + local oc_entry = "" + local search_dirs = { + install_path .. "/global/lib/node_modules/openclaw", + install_path .. "/global/node_modules/openclaw", + install_path .. "/node/lib/node_modules/openclaw", + } + for _, d in ipairs(search_dirs) do + if nixio.fs.stat(d .. "/openclaw.mjs", "type") then + oc_entry = d .. "/openclaw.mjs" + break + elseif nixio.fs.stat(d .. "/dist/cli.js", "type") then + oc_entry = d .. "/dist/cli.js" + break + end + end + if oc_entry == "" or not nixio.fs.stat(node_bin, "type") then + return nil, "OpenClaw 未安装或 Node.js 不可用" + end + local oc_data = install_path .. "/data" + local cmd = string.format( + "ulimit -v unlimited 2>/dev/null || true; " .. + "HOME=%s OPENCLAW_HOME=%s " .. + "OPENCLAW_STATE_DIR=%s/.openclaw " .. + "OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " .. + "NODE_ICU_DATA=%s/node/share/icu " .. + "PATH=%s/node/bin:%s/global/bin:$PATH " .. + "%s %s %s 2>&1; printf '\\n__EXIT:%%d__' $?", + shellquote(oc_data), shellquote(oc_data), + shellquote(oc_data), shellquote(oc_data), + shellquote(install_path), + shellquote(install_path), shellquote(install_path), + shellquote(node_bin), shellquote(oc_entry), args + ) + local output = sys.exec(cmd) + local exit_code = 0 + if output then + local code_str = output:match("__EXIT:(%d+)__") + if code_str then + exit_code = tonumber(code_str) or 0 + output = output:gsub("%s*__EXIT:%d+__%s*$", "") + end + end + return output, exit_code +end + +function action_devices_list() + local http = require "luci.http" + local json = require "luci.jsonc" + + local output, exit_code = run_openclaw_devices_cli("devices list --json") + http.prepare_content("application/json") + if not output then + http.write_json({ status = "error", message = "执行失败", pending = {}, paired = {} }) + return + end + + local json_str = output:match("({%s*\"pending\".*})") or output:match("({.*})") + local data = json_str and json.parse(json_str) or nil + if data and (data.pending or data.paired) then + http.write_json({ + status = "ok", + pending = data.pending or {}, + paired = data.paired or {} + }) + else + http.write_json({ + status = "ok", + pending = {}, + paired = {}, + raw = output:sub(1, 300) + }) + end +end + +function action_devices_approve() + local http = require "luci.http" + local json = require "luci.jsonc" + + local request_id = http.formvalue("request_id") + local approve_all = http.formvalue("all") == "1" or http.formvalue("all") == "true" + + local results = {} + local success_count = 0 + local fail_count = 0 + + if approve_all then + local list_out, list_code = run_openclaw_devices_cli("devices list --json") + local json_str = list_out and (list_out:match("({%s*\"pending\".*})") or list_out:match("({.*})")) + local data = json_str and json.parse(json_str) or nil + local pending = (data and data.pending) or {} + + if #pending == 0 then + http.prepare_content("application/json") + http.write_json({ status = "ok", success_count = 0, fail_count = 0, count = 0, message = "当前没有待配对的设备请求" }) + return + end + + for _, item in ipairs(pending) do + local rid = item.requestId + if rid and rid ~= "" and rid:match("^[a-zA-Z0-9%-_]+$") then + local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(rid)) + local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR")) + if is_ok or (out and out:lower():match("approved")) then + success_count = success_count + 1 + table.insert(results, { requestId = rid, ok = true, message = out }) + else + fail_count = fail_count + 1 + table.insert(results, { requestId = rid, ok = false, message = out or "批准失败" }) + end + end + end + elseif request_id and request_id ~= "" then + if not request_id:match("^[a-zA-Z0-9%-_]+$") then + http.prepare_content("application/json") + http.write_json({ status = "error", message = "无效的请求 ID 格式" }) + return + end + local out, exit_code = run_openclaw_devices_cli("devices approve " .. shellquote(request_id)) + local is_ok = (exit_code == 0) and (out and not out:match("No pending device") and not out:match("Error") and not out:match("ERROR")) + if is_ok or (out and out:lower():match("approved")) then + success_count = 1 + table.insert(results, { requestId = request_id, ok = true, message = out }) + else + fail_count = 1 + table.insert(results, { requestId = request_id, ok = false, message = out or "批准失败" }) + end + else + http.prepare_content("application/json") + http.write_json({ status = "error", message = "缺少 request_id 或 all 参数" }) + return + end + + local install_path = get_install_path() + fix_openclaw_state_permissions(install_path .. "/data") + + http.prepare_content("application/json") + if success_count > 0 then + http.write_json({ + status = "ok", + success_count = success_count, + fail_count = fail_count, + results = results, + message = string.format("成功批准 %d 个设备配对请求%s", success_count, fail_count > 0 and string.format(",%d 个失败", fail_count) or "") + }) + else + http.write_json({ + status = "error", + success_count = 0, + fail_count = fail_count, + results = results, + message = results[1] and results[1].message or "批准设备配对失败" + }) + end +end diff --git a/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua b/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua index 058b4fea..fa2d44da 100644 --- a/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua +++ b/luci-app-openclaw/luasrc/model/cbi/openclaw/basic.lua @@ -54,16 +54,18 @@ act.cfgvalue = function(self, section) html[#html+1] = '

📦 选择安装版本

' html[#html+1] = '
' -- 稳定版选项 + local default_tested_ver = luci.sys.exec("sed -n 's/^OC_TESTED_VERSION=\"\\(.*\\)\"/\\1/p' /usr/bin/openclaw-env 2>/dev/null"):gsub("%s+", "") + if default_tested_ver == "" then default_tested_ver = "2026.9.1" end html[#html+1] = '' -- 最新版选项 html[#html+1] = '