diff --git a/hysteria/Makefile b/hysteria/Makefile index 6b9baf934..2a7906b00 100644 --- a/hysteria/Makefile +++ b/hysteria/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=hysteria -PKG_VERSION:=2.12.1 -PKG_RELEASE:=15 +PKG_VERSION:=2.12.2 +PKG_RELEASE:=16 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)? diff --git a/librespeed-common/Makefile b/librespeed-common/Makefile index 0499e1dad..bd989e676 100644 --- a/librespeed-common/Makefile +++ b/librespeed-common/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=librespeed-common PKG_VERSION:=1.0.0 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_MAINTAINER:=Josef Schlehofer PKG_LICENSE:=GPL-2.0-only @@ -21,7 +21,8 @@ define Package/librespeed-common SECTION:=utils CATEGORY:=Utilities TITLE:=LibreSpeed measurement orchestration - DEPENDS:=+librespeed-cli +rpcd-mod-ucode +jsonfilter + DEPENDS:=+librespeed-cli +rpcd-mod-ucode +jsonfilter +ucode +ucode-mod-fs \ + +ucode-mod-uci +jshn PKGARCH:=all endef diff --git a/librespeed-common/files/librespeed-aggregate b/librespeed-common/files/librespeed-aggregate index 15cd753c4..2c3a078b8 100755 --- a/librespeed-common/files/librespeed-aggregate +++ b/librespeed-common/files/librespeed-aggregate @@ -12,7 +12,7 @@ 'use strict'; -import { open, readfile, writefile, rename, mkdir, error } from 'fs'; +import { open, readfile, rename, mkdir, unlink, error } from 'fs'; // Packaging checks probe every executable for these. if (length(ARGV) > 0) { @@ -176,12 +176,25 @@ const dir = replace(archive_path, /\/[^\/]+$/, ''); if (dir != '' && dir != archive_path) mkdir(dir, 0o755); -// Atomic: a reader never sees a half-written archive. A failed write goes to -// syslog: this runs from cron, where stderr has nowhere to go. The reason -// comes along, since a missing mount, a read-only filesystem and a full disk -// each want something different from whoever reads that log. -if (writefile(tmp, out) != null) +// Atomic: a reader never sees a half-written archive. Written by hand +// rather than writefile(), which drops fclose()'s status and would let a +// full disk truncate the archive in silence -- flush() is where a short +// write surfaces. A failed write goes to syslog: this runs from cron, +// where stderr has nowhere to go. The reason comes along, since a missing +// mount, a read-only filesystem and a full disk each want something +// different from whoever reads that log. +const af = open(tmp, 'w'); +let wrote = af != null && af.write(out) == length(out); + +if (af) { + wrote = af.flush() != null && wrote; + af.close(); +} + +if (wrote) rename(tmp, archive_path); -else +else { system(['logger', '-t', 'librespeed', `aggregate: cannot write ${tmp}: ${error()}`]); + unlink(tmp); +} diff --git a/librespeed-common/files/librespeed-run b/librespeed-common/files/librespeed-run index 8de9059df..49cc20669 100755 --- a/librespeed-common/files/librespeed-run +++ b/librespeed-common/files/librespeed-run @@ -78,9 +78,16 @@ sampler() { else printf "- -" }') prx=$rx; ptx=$tx + # The runner may die without reaching its kill (SIGKILL, a trap that + # never ran); without this check the sampler would rewrite state.json + # forever and mask every later run's result. + kill -0 $$ 2>/dev/null || return 0 + # An idle second keeps the last phase: the frontend reads an empty + # phase as "not started yet" and would collapse mid-run. if [ "$1" = "-" ]; then - write_state 1 $$ "$started" "" "" "" "" + write_state 1 $$ "$started" "" "" "$phase" "" else + phase=$1 write_state 1 $$ "$started" "" "" "$1" "$2" fi done @@ -184,7 +191,9 @@ write_state 1 $$ "$started" # stop kills our whole process group, so librespeed-cli dies with us; ash runs # this trap once the foreground child has exited, and it records that the run # was stopped rather than pretending the measurement failed. -trap 'write_state 0 "" "" "$(date +%s)" "stopped"; exit 1' TERM +trap '[ -n "$sampler_pid" ] && kill "$sampler_pid" 2>/dev/null + write_state 0 "" "" "$(date +%s)" "stopped"; exit 1' TERM +sampler_pid= if [ "$stream" = 1 ]; then # The while runs in a subshell, so the reports cannot come back in a @@ -213,18 +222,23 @@ if [ "$stream" = 1 ]; then # stream delivered: a client that failed never emitted a result event. out=$(cat "$STATE_DIR/reports.json" 2>/dev/null) rm -f "$STATE_DIR/reports.json" - [ -n "$out" ] || \ - fail "$(tail -n 1 "$STATE_DIR/stderr.log" 2>/dev/null || echo 'measurement failed')" + if [ -z "$out" ]; then + # tail succeeds on an empty file, so the substitution guards the + # value, not the command: an empty log still reports a failure. + err=$(tail -n 1 "$STATE_DIR/stderr.log" 2>/dev/null) + fail "${err:-measurement failed}" + fi else - sampler_pid="" if [ -n "$dev" ]; then sampler "$dev" & sampler_pid=$! fi out=$("$@" 2>"$STATE_DIR/stderr.log" < /dev/null); rc=$? [ -n "$sampler_pid" ] && kill "$sampler_pid" 2>/dev/null - [ "$rc" = 0 ] || \ - fail "$(tail -n 1 "$STATE_DIR/stderr.log" 2>/dev/null || echo 'measurement failed')" + if [ "$rc" != 0 ]; then + err=$(tail -n 1 "$STATE_DIR/stderr.log" 2>/dev/null) + fail "${err:-measurement failed (exit $rc)}" + fi fi finished=$(date +%s) @@ -346,9 +360,12 @@ if [ "$hist_enabled" = "1" ]; then # a gigabyte a year through the flash instead of a few megabytes. The # file therefore holds somewhat more than the window, and is trimmed # once enough has accumulated to be worth the write. + # jshn writes '"epoch": 175...' with a space, so the match is + # tolerant and the digits are extracted rather than offset-counted. expired=$(awk -v c="$cutoff" ' - match($0, /"epoch":[0-9]+/) { - if (substr($0, RSTART + 8, RLENGTH - 8) + 0 < c) n++ + match($0, /"epoch":[[:space:]]*[0-9]+/) { + v = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", v) + if (v + 0 < c) n++ } END { print n + 0 } ' "$hist_path") @@ -357,8 +374,9 @@ if [ "$hist_enabled" = "1" ]; then if [ "$expired" -ge 50 ] || [ "$expired" -ge $(( total / 2 )) ] && [ "$expired" -gt 0 ]; then tmp="$hist_path.$$" awk -v c="$cutoff" ' - match($0, /"epoch":[0-9]+/) { - if (substr($0, RSTART + 8, RLENGTH - 8) + 0 < c) next + match($0, /"epoch":[[:space:]]*[0-9]+/) { + v = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", v) + if (v + 0 < c) next } { print } ' "$hist_path" > "$tmp" && mv "$tmp" "$hist_path" diff --git a/librespeed-common/files/librespeed.init b/librespeed-common/files/librespeed.init index 8b9cb959f..a1a87d60e 100644 --- a/librespeed-common/files/librespeed.init +++ b/librespeed-common/files/librespeed.init @@ -15,6 +15,26 @@ AGG=/usr/libexec/librespeed-aggregate # random time is drawn from, drawn here at sync time. That keeps the herd # apart -- every router lands on its own minute -- without any process # sleeping through the night, at the cost of re-drawing on every reload. +# Every comma-separated part must be a number or an ascending range within +# [0, max]; the caller already reduced the alphabet to digits, commas and +# dashes. +valid_field() { + local val="$1" max="$2" part a b oifs="$IFS" + IFS=',' + for part in $val; do + IFS="$oifs" + a=${part%-*}; b=${part#*-} + # Each half on its own: concatenated, '5-' and '-5' hide their empty + # side and the arithmetic below would print a shell error where the + # caller promises a silent fallback. + case "$a" in ''|*[!0-9]*) return 1 ;; esac + case "$b" in ''|*[!0-9]*) return 1 ;; esac + [ "$a" -le "$max" ] && [ "$b" -le "$max" ] && [ "$a" -le "$b" ] || return 1 + done + IFS="$oifs" + return 0 +} + cron_expr() { local interval="$1" days="$2" hours="$3" local start end m h v @@ -77,8 +97,16 @@ sync_cron() { # These end up verbatim in root's crontab, so anything unexpected becomes # the harmless default rather than a cron field it was not meant to be. - case "$days" in *[!0-9,-]*) days='*' ;; '') days='*' ;; esac + # Values are checked as well as characters: '99' is made of digits, but + # as an hour it would turn a daily schedule into an hourly one. + case "$days" in *[!0-9,-]*|'') days='*' ;; esac + if [ "$days" != '*' ] && ! valid_field "$days" 6; then + days='*' + fi case "$hours" in *[!0-9-]*) hours='' ;; esac + if [ -n "$hours" ] && ! valid_field "$hours" 23; then + hours='' + fi mkdir -p "${CRONTAB%/*}" touch "$CRONTAB" diff --git a/librespeed-common/files/librespeed.uc b/librespeed-common/files/librespeed.uc index 261d0d693..fee8b9751 100644 --- a/librespeed-common/files/librespeed.uc +++ b/librespeed-common/files/librespeed.uc @@ -78,6 +78,11 @@ function config_get(uci, section, option, fallback) { // browser: the schedule fires in the router's timezone, and the browser may // well sit in another one. Understands only the shapes librespeed.init // emits: numbers, ranges, ranges with a step, star, and comma lists. +// Hoisted: ucode recompiles a regex literal on every evaluation, and these +// two dominate cron_next's cost inside rpcd, which must never stall. +const CRON_STEP = /^(.+)\/([0-9]+)$/; +const CRON_RANGE = /^([0-9]+)-([0-9]+)$/; + function cron_next(line, count) { const f = split(trim(line ?? ''), /\s+/); @@ -87,7 +92,7 @@ function cron_next(line, count) { const match_field = function(pat, val) { for (let part in split(pat, ',')) { let step = 1; - let m = match(part, /^(.+)\/([0-9]+)$/); + let m = match(part, CRON_STEP); if (m) { part = m[1]; @@ -101,7 +106,7 @@ function cron_next(line, count) { b = 59; } else { - m = match(part, /^([0-9]+)-([0-9]+)$/); + m = match(part, CRON_RANGE); if (m) { a = int(m[1]); b = int(m[2]); @@ -123,14 +128,36 @@ function cron_next(line, count) { let t = time(); t -= t % 60; - for (let i = 0; i < 8 * 24 * 60 && length(out) < count; i++) { + // A miss skips the rest of the day or hour instead of walking its + // minutes; that keeps this cheap inside rpcd's event loop, and cheap + // enough for a five-week horizon, which a weekly schedule needs to + // fill three rows where eight days could not. The day jump aims at + // 23:00, not midnight: it is computed in local minutes but applied as + // real ones, and across a spring-forward that lands an hour long -- + // from 23:00 the hour branch walks the last hour and cannot overshoot + // into minutes that were never examined. + for (let i = 0; i < 35 * 24 * 60 && length(out) < count; ) { t += 60; const lt = localtime(t); + let skip; // % 7 folds both weekday conventions onto cron's 0-6 with Sunday 0. - if (match_field(f[0], lt.min) && match_field(f[1], lt.hour) && - match_field(f[4], lt.wday % 7)) - push(out, t); + if (!match_field(f[4], lt.wday % 7)) { + skip = (24 - lt.hour) * 60 - lt.min - 60; + if (skip <= 0) + skip = 60 - lt.min; + } + else if (!match_field(f[1], lt.hour)) + skip = 60 - lt.min; + else { + if (match_field(f[0], lt.min)) + push(out, t); + i++; + continue; + } + + t += (skip - 1) * 60; + i += skip; } return out; @@ -182,8 +209,12 @@ const methods = { if (st.pid) out.pid = int(st.pid); - if (st.started) + if (st.started) { out.started = int(st.started); + // Elapsed is computed here, on the clock that stamped + // started: the browser's clock may sit anywhere. + out.elapsed = time() - int(st.started); + } if (st.mbps != null) out.mbps = st.mbps + 0.0; if (st.progress != null) @@ -297,7 +328,11 @@ const methods = { scheme: config_get(uci, 'main', 'scheme', 'auto'), server_list: config_get(uci, 'main', 'server_list', ''), schedule: { - enabled: config_get(uci, 'schedule', 'enabled', '0') == '1', + // What the crontab holds, not what UCI intends: a + // hand-set 'true' satisfies the init script's bool but + // not a string compare, and the page would say No while + // cron fires. The line is the one source of truth. + enabled: cron != '', interval: config_get(uci, 'schedule', 'interval', '1d'), days: config_get(uci, 'schedule', 'days', '*'), hours: config_get(uci, 'schedule', 'hours', ''), diff --git a/librespeed-common/test.sh b/librespeed-common/test.sh index a081e8138..bc33849d6 100755 --- a/librespeed-common/test.sh +++ b/librespeed-common/test.sh @@ -23,4 +23,13 @@ sh -n /etc/init.d/librespeed || fail "init script does not parse" ucode /usr/share/rpcd/ucode/librespeed.uc >/dev/null \ || fail "rpcd plugin does not load" +# Retention must recognise epoch as jshn actually writes it -- with a space +# after the colon. The fixture comes from json_dump itself, so the check +# breaks if either side changes shape. +line=$(. /usr/share/libubox/jshn.sh; json_init; json_add_int epoch 1; json_dump) \ + || fail "jshn not usable" +echo "$line" | awk 'match($0, /"epoch":[[:space:]]*[0-9]+/) { ok = 1 } + { print } + END { exit !ok }' || fail "retention regex does not match jshn output" + echo "librespeed-common: installed files OK" diff --git a/linkease-common-bin/Makefile b/linkease-common-bin/Makefile index c65c1a54f..0bbef5236 100644 --- a/linkease-common-bin/Makefile +++ b/linkease-common-bin/Makefile @@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease-common-bin # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable PKG_SOURCE_DATE:=1.7.5 -PKG_RELEASE:=13 +PKG_RELEASE:=14 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel) ARCH_HEXCODE=1b0c endif -PKG_SOURCE_VERSION:=70c21864a1e95ece2ae6e4b144ce2d7a68d0405f +PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE) diff --git a/linkease/Makefile b/linkease/Makefile index 3d33e3d44..c5109c100 100644 --- a/linkease/Makefile +++ b/linkease/Makefile @@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkease # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable PKG_SOURCE_DATE:=1.7.5 -PKG_RELEASE:=16 +PKG_RELEASE:=17 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel) ARCH_HEXCODE=1b0c endif -PKG_SOURCE_VERSION:=70c21864a1e95ece2ae6e4b144ce2d7a68d0405f +PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE) diff --git a/linkeasefull/Makefile b/linkeasefull/Makefile index e9e4f31f8..53a042c04 100644 --- a/linkeasefull/Makefile +++ b/linkeasefull/Makefile @@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH) PKG_NAME:=linkeasefull # use PKG_SOURCE_DATE instead of PKG_VERSION for compitable PKG_SOURCE_DATE:=3.0.12 -PKG_RELEASE:=15 +PKG_RELEASE:=16 ARCH_HEXCODE:= ifeq ($(ARCH),x86_64) @@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64 PKG_HASH:=skip endif -PKG_SOURCE_VERSION:=70c21864a1e95ece2ae6e4b144ce2d7a68d0405f +PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/ PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH) diff --git a/luci-app-arcadia/Makefile b/luci-app-arcadia/Makefile index 24b7704f9..cd7098337 100644 --- a/luci-app-arcadia/Makefile +++ b/luci-app-arcadia/Makefile @@ -2,8 +2,8 @@ include $(TOPDIR)/rules.mk -PKG_VERSION:=1.0.0-r20260804 -PKG_RELEASE:=5 +PKG_VERSION:=1.0.0-r20260824 +PKG_RELEASE:=6 LUCI_TITLE:=LuCI support for arcadia LUCI_PKGARCH:=all diff --git a/luci-app-arcadia/luasrc/model/cbi/arcadia.lua b/luci-app-arcadia/luasrc/model/cbi/arcadia.lua index 3acdc165a..15e3fbe2f 100644 --- a/luci-app-arcadia/luasrc/model/cbi/arcadia.lua +++ b/luci-app-arcadia/luasrc/model/cbi/arcadia.lua @@ -8,10 +8,9 @@ local m, s, o m = taskd.docker_map("arcadia", "arcadia", "/usr/libexec/istorec/arcadia.sh", translate("Arcadia"), - translate("Arcadia one-stop code operation and maintenance platform.") + translate("Arcadia · One-Stop Code Automation & DevOps Platform.") .. translate("Official website:") .. ' https://arcadia.cool' - .. "
" .. translate("Arcadia is mainly aimed at scripting language programming and operation and maintenance, supports scheduled task scheduling, has a complete file system and underlying CLI command design, and is suitable for the development and operation and maintenance environment of small and medium-sized teams and individuals.") .. "
" - .. "
" .. translate("The project is based on TypeScript full-stack development and adopts many cutting-edge technologies. Vue + Vite is used on the front end, and Node.js + Express + Prisma ORM is used on the back end.") .. "
" + .. "
" .. translate("Arcadia is designed for script-based programming and operations scenarios. It provides capabilities including scheduled task scheduling, multi-language code execution, a comprehensive file system, and well-designed underlying CLI commands. It is suitable for individual developers and small to medium-sized teams who seek to improve development and operations efficiency through automated scripting.") .. "
" .. "
" .. translate("The initial username and password are 'useradmin' and 'passwd' respectively.") .. "
" .. "
") @@ -27,11 +26,11 @@ s.anonymous=true o = s:option(Value, "image", translate("Docker Image")) o.datatype = "string" o:value("", translate("Default")) -o:value("supermanito/arcadia:beta", "supermanito/arcadia:beta") -o:value("registry.cn-hangzhou.aliyuncs.com/supermanito/arcadia:beta", "registry.cn-hangzhou.aliyuncs.com/supermanito/arcadia:beta") -o:value("docker.m.daocloud.io/supermanito/arcadia:beta", "docker.m.daocloud.io/supermanito/arcadia:beta") -o:value("docker.1ms.run/supermanito/arcadia:beta", "docker.1ms.run/supermanito/arcadia:beta") -o:value("docker.xuanyuan.me/supermanito/arcadia:beta", "docker.xuanyuan.me/supermanito/arcadia:beta") +o:value("supermanito/arcadia", "supermanito/arcadia") +o:value("registry.cn-hangzhou.aliyuncs.com/supermanito/arcadia", "registry.cn-hangzhou.aliyuncs.com/supermanito/arcadia") +o:value("docker.m.daocloud.io/supermanito/arcadia", "docker.m.daocloud.io/supermanito/arcadia") +o:value("docker.1ms.run/supermanito/arcadia", "docker.1ms.run/supermanito/arcadia") +o:value("docker.xuanyuan.me/supermanito/arcadia", "docker.xuanyuan.me/supermanito/arcadia") o = s:option(Flag, "hostnet", translate("Host network"), translate("Arcadia running in host network, port is always 5678 if enabled")) o.default = 0 diff --git a/luci-app-arcadia/po/zh-cn/arcadia.po b/luci-app-arcadia/po/zh-cn/arcadia.po index bc840a9d3..a1e7c7fb0 100644 --- a/luci-app-arcadia/po/zh-cn/arcadia.po +++ b/luci-app-arcadia/po/zh-cn/arcadia.po @@ -4,17 +4,14 @@ msgstr "Content-Type: text/plain; charset=UTF-8" msgid "Official website:" msgstr "官方网站:" -msgid "Arcadia one-stop code operation and maintenance platform." -msgstr "Arcadia 一站式代码运维平台。" +msgid "Arcadia · One-Stop Code Automation & DevOps Platform." +msgstr "Arcadia 一站式代码自动化运维平台。" -msgid "Arcadia is mainly aimed at scripting language programming and operation and maintenance, supports scheduled task scheduling, has a complete file system and underlying CLI command design, and is suitable for the development and operation and maintenance environment of small and medium-sized teams and individuals." -msgstr "Arcadia 主要面向于脚本语言编程与运维,支持定时任务调度,有着完善的文件系统和底层CLI命令设计,适用于中小型团队与个人的开发与运维环境。" - -msgid "The project is based on TypeScript full-stack development and adopts many cutting-edge technologies. Vue + Vite is used on the front end, and Node.js + Express + Prisma ORM is used on the back end." -msgstr "项目基于 TypeScript 全栈开发,采用了众多前沿技术,前端使用 Vue + Vite,后端使用 Node.js + Express + Prisma ORM。" +msgid "Arcadia is designed for script-based programming and operations scenarios. It provides capabilities including scheduled task scheduling, multi-language code execution, a comprehensive file system, and well-designed underlying CLI commands. It is suitable for individual developers and small to medium-sized teams who seek to improve development and operations efficiency through automated scripting." +msgstr "Arcadia 平台面向脚本语言编程与运维场景,提供定时任务调度、多语言代码执行、完善的文件系统与底层 CLI 命令设计等能力,适用于希望借助自动化脚本提升开发运维效率的个人开发者与中小型团队。" msgid "The initial username and password are 'useradmin' and 'passwd' respectively." -msgstr "初始的用户名和密码分别是'useradmin'和'passwd'。" +msgstr "初始的用户名和密码分别是 'useradmin' 和 'passwd'。" msgid "Docker Image" msgstr "镜像" diff --git a/luci-app-arcadia/root/usr/libexec/istorec/arcadia.sh b/luci-app-arcadia/root/usr/libexec/istorec/arcadia.sh index bce76773c..464bb4150 100755 --- a/luci-app-arcadia/root/usr/libexec/istorec/arcadia.sh +++ b/luci-app-arcadia/root/usr/libexec/istorec/arcadia.sh @@ -11,7 +11,7 @@ IMAGE_NAME='default' get_image() { IMAGE_NAME=`uci get arcadia.@arcadia[0].image 2>/dev/null` if [ -z "$IMAGE_NAME" -o "$IMAGE_NAME" == "default" ]; then - IMAGE_NAME="supermanito/arcadia:beta" + IMAGE_NAME="supermanito/arcadia" fi } @@ -37,7 +37,7 @@ do_install_detail() { [ -z "$port" ] && port=5678 - local cmd="docker run --restart=unless-stopped -d -v \"$config/config:/arcadia/config\" -v \"$config/log:/arcadia/log\" -v \"$config/scripts:/arcadia/scripts\" -v \"$config/repo:/arcadia/repo\" -v \"$config/raw:/arcadia/raw\" -v \"$config/tgbot:/arcadia/tgbot\" " + local cmd="docker run --restart=unless-stopped -d --cap-add SYS_PTRACE -v \"$config/config:/arcadia/config\" -v \"$config/log:/arcadia/log\" -v \"$config/scripts:/arcadia/scripts\" -v \"$config/repo:/arcadia/repo\" -v \"$config/raw:/arcadia/raw\" -v \"$config/tgbot:/arcadia/tgbot\" " if [ "$hostnet" = 1 ]; then cmd="$cmd\ --dns=127.0.0.1 \ diff --git a/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js b/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js index b327ae27e..d1eb02edc 100644 --- a/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js +++ b/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js @@ -46,10 +46,6 @@ function download(name, mime, text) { window.URL.revokeObjectURL(url); } -/* Bumped whenever librespeed.css changes, so a routine page load picks the - * new stylesheet up instead of pairing new views with a cached old one. */ -const CSS_VERSION = '18'; - function metricOf(key) { return METRICS.find(m => m[0] == key); } @@ -142,46 +138,79 @@ return baseclass.extend({ GROUPS: GROUPS, RANGES: RANGES, - /* The app stylesheet, cache-busted by its own version: LuCI's resource - * version only changes when luci-base does, which is not when we do. */ + /* Linked plainly, the way the other applications do: uhttpd serves + * static resources with ETag revalidation, so an upgraded stylesheet + * reaches the browser without a cache-busting version to maintain. */ cssLink() { return E('link', { 'rel': 'stylesheet', - 'href': L.resource('librespeed/librespeed.css') + '?v=' + CSS_VERSION + 'href': L.resource('librespeed/librespeed.css') }); }, + /* One token->label table for the schedule intervals: Settings builds + * its choices from it and the Test page looks the status label up, so + * the two cannot drift. The init script accepts more shapes than these + * six, so readers must fall back to the raw token. */ + INTERVALS: { + '15m': _('Every 15 minutes'), + '30m': _('Every 30 minutes'), + '1h': _('Hourly'), + '6h': _('Every 6 hours'), + '12h': _('Every 12 hours'), + '1d': _('Daily') + }, + + /* Exported alongside the chart: every timestamp a page prints should + * come through here, or the date-only guard gets lost in a copy. */ + chartStampFull: chartStampFull, + /* Plain figures of one series: what is normal, how much it wobbles, and - * where it stands now. The story sentences are built from these. */ - seriesStats(entries, metric) { + * where it stands now. The story sentences are built from these. On + * daily aggregates the extremes come from the _min/_max columns the + * chart also draws -- min/max of the daily means would understate the + * spread and contradict the band directly above the sentence. */ + seriesStats(entries, metric, resolution) { const vals = (entries || []).map(e => e[metric]).filter(v => typeof v == 'number'); if (!vals.length) return null; const avg = vals.reduce((a, b) => a + b, 0) / vals.length; + const lows = (resolution == '1d') + ? (entries || []).map(e => typeof e[metric + '_min'] == 'number' + ? e[metric + '_min'] : e[metric]).filter(v => typeof v == 'number') + : vals; + const highs = (resolution == '1d') + ? (entries || []).map(e => typeof e[metric + '_max'] == 'number' + ? e[metric + '_max'] : e[metric]).filter(v => typeof v == 'number') + : vals; return { count: vals.length, avg: avg, - min: Math.min.apply(null, vals), - max: Math.max.apply(null, vals), + min: Math.min.apply(null, lows), + max: Math.max.apply(null, highs), current: vals[vals.length - 1] }; }, - /* A row of toggle buttons; the active one is highlighted. */ - switcher(view, items, current, onpick) { - return E('div', {}, items.map(it => - E('button', { - 'class': it[0] == current ? 'cbi-button cbi-button-action' : 'cbi-button', - 'style': 'margin-right:.3em', - 'click': ui.createHandlerFn(view, function() { - return onpick.call(view, it[0]); - }) - /* Callers hand in display-ready labels: a runtime _() here - * would both miss the extractor and translate twice. */ - }, [ it[1] != null ? it[1] : it[0] ]))); + /* A row of toggle buttons; the active one is highlighted. The state is + * also spoken: colour alone says nothing to a screen reader, so each + * button carries aria-pressed and the group a name for what it picks. */ + switcher(view, items, current, onpick, label) { + return E('div', { 'role': 'group', 'aria-label': label || null }, + items.map(it => + E('button', { + 'class': it[0] == current ? 'cbi-button cbi-button-action' : 'cbi-button', + 'style': 'margin-right:.3em', + 'aria-pressed': it[0] == current ? 'true' : 'false', + 'click': ui.createHandlerFn(view, function() { + return onpick.call(view, it[0]); + }) + /* Callers hand in display-ready labels: a runtime _() here + * would both miss the extractor and translate twice. */ + }, [ it[1] != null ? it[1] : it[0] ]))); }, /* The one chart component: hand-built SVG, the way the realtime status @@ -254,8 +283,9 @@ return baseclass.extend({ const avg = valid.reduce((a, p) => a + p[2], 0) / valid.length; body += '' .format(ci, L, sc.ys(avg), W - R, sc.ys(avg)); - body += 'avg %s' - .format(W - R, sc.ys(avg) - 4, avg.toFixed(avg >= 100 ? 0 : 1)); + body += '%h %s' + .format(W - R, sc.ys(avg) - 4, _('avg'), + avg.toFixed(avg >= 100 ? 0 : 1)); body += (valid.length > 1) ? ''.format(ci, pts) @@ -314,7 +344,7 @@ return baseclass.extend({ opts.legend.all.forEach(k => { const m = metricOf(k); - const st = this.seriesStats(entries, k); + const st = this.seriesStats(entries, k, opts.resolution); const cb = E('input', { 'type': 'checkbox' }); cb.checked = opts.series.indexOf(k) >= 0; @@ -426,7 +456,8 @@ return baseclass.extend({ if (typeof e[k] != 'number') return; let txt = fmtVal(e[k], m[2]); - if (typeof e[k + '_min'] == 'number') + if (typeof e[k + '_min'] == 'number' && + typeof e[k + '_max'] == 'number') txt += ' (%s – %s)'.format( e[k + '_min'].toFixed(m[2] == 'Mbps' ? 0 : 1), e[k + '_max'].toFixed(m[2] == 'Mbps' ? 0 : 1)); diff --git a/luci-app-librespeed/htdocs/luci-static/resources/librespeed/librespeed.css b/luci-app-librespeed/htdocs/luci-static/resources/librespeed/librespeed.css index 2b5dfea5d..75b1f0de6 100644 --- a/luci-app-librespeed/htdocs/luci-static/resources/librespeed/librespeed.css +++ b/luci-app-librespeed/htdocs/luci-static/resources/librespeed/librespeed.css @@ -1,3 +1,19 @@ +/* The theme's accent where the theme publishes one: --primary-color-medium + * exists only in luci-theme-bootstrap. Every other in-tree theme sees the + * fallback, which must clear 3:1 against a light page -- the arc and the + * history line are the only graphical encoding of the data. */ +:root { + --librespeed-accent: var(--primary-color-medium, #3c8dbc); +} + +/* Dark is an attribute in LuCI, not a media preference: bootstrap stamps + * data-darkmode on the root and pins it for its light/dark variants, while + * the themes that actually reach the fallback have no dark mode at all -- + * an OS-level preference must not darken their light pages. */ +:root[data-darkmode="true"] { + --librespeed-accent: var(--primary-color-medium, #6ab0de); +} + /* The one piece of CSS this application owns: layout and type scale. Colors, * borders, buttons and spacing inside sections stay with the theme. */ @@ -55,31 +71,31 @@ max-width: 20em; display: inline-block; vertical-align: middle; - accent-color: var(--primary-color-medium, #5bc0de); + accent-color: var(--librespeed-accent); } /* The chart takes its color from the theme where one is exposed, and falls * back to a neutral accent where none is. */ .librespeed-chart-line { - stroke: var(--primary-color-medium, #5bc0de); + stroke: var(--librespeed-accent); fill: none; stroke-width: 1.5; } .librespeed-chart-fill { - fill: var(--primary-color-medium, #5bc0de); + fill: var(--librespeed-accent); fill-opacity: .1; stroke: none; } .librespeed-chart-band { - fill: var(--primary-color-medium, #5bc0de); + fill: var(--librespeed-accent); fill-opacity: .18; stroke: none; } .librespeed-chart-dot { - fill: var(--primary-color-medium, #5bc0de); + fill: var(--librespeed-accent); } .librespeed-toolbar { @@ -138,7 +154,7 @@ .librespeed-gauge-fill { fill: none; - stroke: var(--primary-color-medium, #5bc0de); + stroke: var(--librespeed-accent); stroke-width: 12; stroke-linecap: round; } @@ -219,17 +235,17 @@ /* One theme color for every series: the second one differs by pattern, not * by a hardcoded hue -- a dashed line and a striped swatch survive any theme * and read for color-blind users too. */ -.librespeed-stroke-0 { stroke: var(--primary-color-medium, #5bc0de); } -.librespeed-stroke-1 { stroke: var(--primary-color-medium, #5bc0de); } +.librespeed-stroke-0 { stroke: var(--librespeed-accent); } +.librespeed-stroke-1 { stroke: var(--librespeed-accent); } polyline.librespeed-stroke-1 { stroke-dasharray: 6 3; } .librespeed-avgline.librespeed-stroke-1 { stroke-dasharray: 2 4; } -.librespeed-fill-0 { fill: var(--primary-color-medium, #5bc0de); } -.librespeed-fill-1 { fill: var(--primary-color-medium, #5bc0de); } +.librespeed-fill-0 { fill: var(--librespeed-accent); } +.librespeed-fill-1 { fill: var(--librespeed-accent); } circle.librespeed-fill-1 { fill-opacity: .55; } -.librespeed-bg-0 { background: var(--primary-color-medium, #5bc0de); } +.librespeed-bg-0 { background: var(--librespeed-accent); } .librespeed-bg-1 { background: repeating-linear-gradient(90deg, - var(--primary-color-medium, #5bc0de) 0 4px, transparent 4px 7px); + var(--librespeed-accent) 0 4px, transparent 4px 7px); } .librespeed-legend { @@ -266,8 +282,8 @@ circle.librespeed-fill-1 { fill-opacity: .55; } margin: 0 auto; } -.librespeed-fg-0 { color: var(--primary-color-medium, #5bc0de); } -.librespeed-fg-1 { color: var(--primary-color-medium, #5bc0de); } +.librespeed-fg-0 { color: var(--librespeed-accent); } +.librespeed-fg-1 { color: var(--librespeed-accent); } .librespeed-cards { display: flex; @@ -283,15 +299,17 @@ circle.librespeed-fill-1 { fill-opacity: .55; } padding: .45em .9em; min-width: 11em; cursor: pointer; - opacity: .5; + /* Dim, still legible: .5 with the muted name inside multiplied down to + * an unreadable third. */ + opacity: .78; user-select: none; } .librespeed-metric-card-on { opacity: 1; } .librespeed-metric-card:hover { border-color: rgba(128, 128, 128, .45); } -.librespeed-accent-0 { border-top-color: var(--primary-color-medium, #5bc0de); } -.librespeed-accent-1 { border-top-color: var(--primary-color-medium, #5bc0de); border-top-style: dashed; } +.librespeed-accent-0 { border-top-color: var(--librespeed-accent); } +.librespeed-accent-1 { border-top-color: var(--librespeed-accent); border-top-style: dashed; } .librespeed-card-value { font-size: 1.3em; @@ -313,3 +331,14 @@ circle.librespeed-fill-1 { fill-opacity: .55; } text-align: left; font-size: .95em; } + +/* Off-screen but reachable: the live region announcing the running phase, + * and the gauge figures repeated as plain text for browse mode. */ +.librespeed-visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} diff --git a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js index c2e3eaf72..5a87c7c7b 100644 --- a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js +++ b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js @@ -14,6 +14,12 @@ * second fetch, since the entries are in memory anyway. */ const pageSize = 50; +const callConfig = rpc.declare({ + object: 'librespeed', + method: 'config', + expect: { '': {} } +}); + const callHistory = rpc.declare({ object: 'librespeed', method: 'history', @@ -61,7 +67,13 @@ return view.extend({ speed: lscommon.GROUPS.speed.slice(), latency: lscommon.GROUPS.latency.slice() }; - return this.fetch(); + /* The config rides along for one purpose: with history keeping off, + * the empty state must not send the user to run a test that will + * never produce an entry. */ + return Promise.all([ + this.fetch(), + callConfig().then(c => { this.config = c; }, () => {}) + ]); }, /* The server/interface filters narrow the fetched range on the client; @@ -107,8 +119,8 @@ return view.extend({ lscommon.GROUPS[this.group].forEach(L.bind(function(k, ki) { const m = lscommon.METRICS.find(x => x[0] == k), - st = lscommon.seriesStats(entries, k), - ps = lscommon.seriesStats(prev, k), + st = lscommon.seriesStats(entries, k, this.resolution), + ps = lscommon.seriesStats(prev, k, this.prevResolution), on = act.indexOf(k) >= 0; let diff = ' '; @@ -146,7 +158,9 @@ return view.extend({ (on ? ' librespeed-metric-card-on' : ''), 'title': on ? _('Hide this series') : _('Show this series') }, [ - E('div', { 'class': 'librespeed-muted librespeed-caps' }, + /* Not muted: the card's own off-state opacity already dims + * it, and two factors multiply into illegibility. */ + E('div', { 'class': 'librespeed-caps' }, [ cb, ' ', m[1] ]), E('div', { 'class': 'librespeed-card-value' }, [ st ? '%.1f'.format(st.avg) + ' ' + m[2] : '–', @@ -189,10 +203,24 @@ return view.extend({ const noneAtAll = !this.entries.length; if (!entries.length) { - this.emptyText.textContent = noneAtAll - ? _('Run a speed test to start building your connection history.') - : _('No measurements match the current filters.'); + /* Strict, matching test.js: the backend emits a real boolean + * (derived from UCI, never its raw '0'/'1'), and an absent or + * failed config must read as "history on", not off. */ + const histOff = noneAtAll && this.config && + this.config.history && this.config.history.enabled === false; + + /* With history keeping off, a test would never write an entry: + * the way out is Settings, not the Start button. */ + this.emptyText.textContent = histOff + ? _('History keeping is switched off, so measurements are not recorded.') + : (noneAtAll + ? _('Run a speed test to start building your connection history.') + : _('No measurements match the current filters.')); this.emptyStart.style.display = noneAtAll ? '' : 'none'; + this.emptyStart.href = histOff + ? L.url('admin', 'network', 'librespeed', 'settings') + : L.url('admin', 'network', 'librespeed', 'test'); + this.emptyStart.textContent = histOff ? _('Open settings') : _('Start test'); this.emptyNode.style.display = ''; this.dataNode.style.display = 'none'; return; @@ -220,17 +248,12 @@ return view.extend({ this.chartNode.appendChild(E('p', { 'class': 'librespeed-muted' }, [ _('Daily minimum, average and maximum.') ])); - const num = v => (typeof v == 'number') ? v : -1; + /* The sort key carries the same decimal count as the display half: + * ui.Table stringifies the whole cell and compares digit runs one by + * one, so ragged fractions would put 94.4 above 94.35. */ + const num = (v, d) => (typeof v == 'number') ? v.toFixed(d) : ''; const fmtNum = (v, d) => (typeof v == 'number') ? v.toFixed(d) : '–'; - const when = e => { - if (!e.timestamp) - return '?'; - if (this.resolution == '1d' || e.timestamp.indexOf('T') < 0) - return e.timestamp; - const d = new Date(e.timestamp); - /* A garbled timestamp shows as itself, never as "Invalid Date". */ - return isNaN(d.getTime()) ? e.timestamp : d.toLocaleString(); - }; + const when = e => lscommon.chartStampFull(e, this.resolution); /* One column carries both what the packets were (IPv4/IPv6) and how * they travelled (HTTPS/HTTP); either half may be unknown. */ @@ -244,15 +267,19 @@ return view.extend({ * cell, and ui.Table only ever orders the rows it was handed, so * paging first would turn the headers into a per-page sort while the * count below still speaks for every measurement. */ + /* Every display half is a node: ui.Table writes bare strings into + * innerHTML, and the server name comes from a downloaded list -- + * text must stay text. The sort key stays the first element. */ + const cell = t => E('span', {}, [ t ]); let rows = entries.slice().reverse().map(e => [ - [ e.epoch ?? 0, when(e) ], - (e.server && e.server.name) || '–', - e.interface || '–', - protoCell(e), - [ num(e.download_mbps), fmtNum(e.download_mbps, 2) ], - [ num(e.upload_mbps), fmtNum(e.upload_mbps, 2) ], - [ num(e.ping_ms), fmtNum(e.ping_ms, 1) ], - [ num(e.jitter_ms), fmtNum(e.jitter_ms, 1) ] + [ e.epoch ?? 0, cell(when(e)) ], + cell((e.server && e.server.name) || '–'), + cell(e.interface || '–'), + cell(protoCell(e)), + [ num(e.download_mbps, 2), cell(fmtNum(e.download_mbps, 2)) ], + [ num(e.upload_mbps, 2), cell(fmtNum(e.upload_mbps, 2)) ], + [ num(e.ping_ms, 1), cell(fmtNum(e.ping_ms, 1)) ], + [ num(e.jitter_ms, 1), cell(fmtNum(e.jitter_ms, 1)) ] ]); const sorting = this.table.getActiveSortState(); @@ -274,7 +301,6 @@ return view.extend({ this.renderPager(rows.length, pages); this.table.update(rows.slice(this.page * pageSize, (this.page + 1) * pageSize)); - }, /* Shown only when there is more than one page; the count is always @@ -286,7 +312,7 @@ return view.extend({ }, this); const nav = [ E('span', { 'class': 'librespeed-muted' }, - [ _('%d measurements').format(total) ]) ]; + [ N_(total, '%d measurement', '%d measurements').format(total) ]) ]; if (pages > 1) { nav.push(E('button', { @@ -341,7 +367,7 @@ return view.extend({ const renderControls = L.bind(function() { const groups = lscommon.switcher(this, [ [ 'speed', _('Speed') ], [ 'latency', _('Latency') ] ], this.group, - function(v) { this.group = v; renderControls(); this.redraw(); }); + function(v) { this.group = v; renderControls(); this.redraw(); }, _('Series')); const ranges = lscommon.switcher(this, lscommon.RANGES.map(r => [ String(r[1]), r[0] ]), String(this.range), function(v) { @@ -352,7 +378,7 @@ return view.extend({ renderControls(); this.redraw(); }, this)); - }); + }, _('Range')); ranges.classList.add('librespeed-push'); @@ -367,6 +393,17 @@ return view.extend({ ifaces.push(e.interface); }); + /* The freshly fetched window may no longer contain the chosen + * server or interface -- daily aggregates carry neither field at + * all. The widget would then quietly repaint as "All" while the + * model kept filtering everything out, with no control on screen + * able to clear it; reconcile the model here, where the choice + * lists are built. */ + if (this.server && servers.indexOf(this.server) < 0) + this.server = ''; + if (this.iface && ifaces.indexOf(this.iface) < 0) + this.iface = ''; + const filters = E('div', { 'class': 'librespeed-toolbar' }, [ this.filterSelect(_('Server'), servers.sort(), this.server, function(v) { this.server = v; this.page = 0; this.redraw(); }), diff --git a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js index 0199d5b10..4f75b6705 100644 --- a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js +++ b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js @@ -3,6 +3,7 @@ 'require form'; 'require rpc'; 'require tools.widgets as widgets'; +'require librespeed.common as lscommon'; const callConfig = rpc.declare({ object: 'librespeed', @@ -59,12 +60,10 @@ return view.extend({ _('Runs a measurement from cron at the chosen interval. Note that a measurement saturates the connection while it runs.')); o = s.option(form.ListValue, 'interval', _('Interval')); - o.value('15m', _('Every 15 minutes')); - o.value('30m', _('Every 30 minutes')); - o.value('1h', _('Hourly')); - o.value('6h', _('Every 6 hours')); - o.value('12h', _('Every 12 hours')); - o.value('1d', _('Daily')); + /* From the shared table, so the Test page's status label and these + * choices cannot drift apart. */ + Object.keys(lscommon.INTERVALS).forEach(k => + o.value(k, lscommon.INTERVALS[k])); o.default = '1d'; o.depends('enabled', '1'); @@ -163,8 +162,11 @@ return view.extend({ ])); } - node.appendChild(box); - return node; + /* A sibling of the form, not a child: Map.render() replaces its + * own root on every Save and Reset, and would take the schedule + * preview with it. The preview reads the applied crontab, so it + * is correct to keep it as it is until Apply. */ + return E([], [ node, box ]); }); } }); diff --git a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js index baedb8ce7..36bd6bedf 100644 --- a/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js +++ b/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js @@ -220,7 +220,8 @@ return view.extend({ this.recentNode.innerHTML = ''; this.recentCount.textContent = entries.length - ? _('%d measurements').format(entries.length) : ''; + ? N_(entries.length, '%d measurement', '%d measurements') + .format(entries.length) : ''; /* The active button names the metric, so the subtitle only carries * the window. */ @@ -232,12 +233,12 @@ return view.extend({ function(v) { this.recentMetric = v; this.renderRecent(this.recentData); - }).childNodes); + }, _('Metric')).childNodes); /* The stability story of the last day, told in one sentence and four * figures: what is normal, how much it wobbles, where it is now. */ const metric = this.recentMetric || 'download_mbps'; - const st = lscommon.seriesStats(entries, metric); + const st = lscommon.seriesStats(entries, metric, data && data.resolution); if (st && st.count > 1) { const varPct = (st.max - st.min) / st.avg * 100, @@ -297,20 +298,26 @@ return view.extend({ } const fmtNum = (v, d) => (typeof v == 'number') ? v.toFixed(d) : '–'; - const when = e => { - const d = new Date(e.timestamp); - return isNaN(d.getTime()) ? String(e.timestamp || '?') : d.toLocaleString(); - }; + /* The shared formatter carries the date-only guard: a daily + * aggregate's bare date fed to new Date() is read as UTC midnight + * and shifts a calendar day in negative offsets. */ + const when = e => lscommon.chartStampFull(e, data && data.resolution); - const num = v => (typeof v == 'number') ? v : -1; + /* The sort key carries the same decimal count as the display half: + * ui.Table stringifies the whole cell and compares digit runs one by + * one, so ragged fractions would put 94.4 above 94.35. */ + const num = (v, d) => (typeof v == 'number') ? v.toFixed(d) : ''; + /* Nodes, not strings: ui.Table writes bare strings into innerHTML + * and the server name comes from a downloaded list. */ + const cell = t => E('span', {}, [ t ]); this.recentTable.update(entries.slice(-recentRows).reverse().map(e => [ - [ e.epoch ?? 0, when(e) ], - (e.server && e.server.name) || '–', - [ num(e.download_mbps), fmtNum(e.download_mbps, 2) ], - [ num(e.upload_mbps), fmtNum(e.upload_mbps, 2) ], - [ num(e.ping_ms), fmtNum(e.ping_ms, 1) ], - [ num(e.jitter_ms), fmtNum(e.jitter_ms, 1) ] + [ e.epoch ?? 0, cell(when(e)) ], + cell((e.server && e.server.name) || '–'), + [ num(e.download_mbps, 2), cell(fmtNum(e.download_mbps, 2)) ], + [ num(e.upload_mbps, 2), cell(fmtNum(e.upload_mbps, 2)) ], + [ num(e.ping_ms, 1), cell(fmtNum(e.ping_ms, 1)) ], + [ num(e.jitter_ms, 1), cell(fmtNum(e.jitter_ms, 1)) ] ])); this.recentNode.appendChild(this.recentTableNode); @@ -374,6 +381,17 @@ return view.extend({ this.resultHeading.textContent = (!status.running && status.last_error) ? _('Last successful measurement') : ''; + /* One quiet live region, outside the tree that is rebuilt every + * poll: announcing the rebuilt gauge SVG would read the whole dial + * out several times a run. Set only on change, so it speaks once + * per phase step. */ + const announce = status.running + ? (PHASES.find(p => p[0] == status.phase) || [])[3] || _('Connecting to the test server…') + : ''; + + if (this.liveNode.textContent != announce) + this.liveNode.textContent = announce; + if (status.running) { const rows = []; @@ -385,7 +403,10 @@ return view.extend({ PHASES.map((p, i) => E('span', { 'class': 'librespeed-step' + (i == phaseIdx ? ' librespeed-step-active' : - (phaseIdx >= 0 && i < phaseIdx ? ' librespeed-step-done' : '')) + (phaseIdx >= 0 && i < phaseIdx ? ' librespeed-step-done' : '')), + /* Spoken as well as dimmed: opacity is invisible to a + * screen reader. */ + 'aria-current': i == phaseIdx ? 'step' : null }, [ p[3] ])))); /* The dial range is frozen at the start of the run -- a familiar @@ -404,13 +425,20 @@ return view.extend({ rows.push(E('p', { 'class': 'spinning', 'style': 'margin-top:.75em' }, [ _('Connecting to the test server…') ])); else { - const holder = E('div', {}); + /* The dial is hidden from assistive tech -- its ten tick + * labels are noise there -- so the one figure it carries, + * the running rate, is repeated as plain hidden text that + * browse mode can still reach. */ + const holder = E('div', { 'aria-hidden': 'true' }); holder.innerHTML = gauge( (typeof status.mbps == 'number' && status.mbps > 0) ? status.mbps : null, this.runTop, status.phase == 'upload' ? 1 : 0); rows.push(holder); + if (typeof status.mbps == 'number' && status.mbps > 0) + rows.push(E('span', { 'class': 'librespeed-visually-hidden' }, + [ '%.2f Mbps'.format(status.mbps) ])); } /* Completion is a different number than the rate on the arc, so @@ -422,8 +450,13 @@ return view.extend({ rows.push(E('div', { 'class': 'librespeed-muted' }, [ overall + ' %' ])); - const elapsed = status.started - ? fmtElapsed(Date.now() / 1000 - status.started) : ''; + /* The backend counts on the clock that stamped the start; the + * difference of two unrelated clocks can be negative. The old + * subtraction stays as a fallback for an updating router. */ + const elapsed = typeof status.elapsed == 'number' + ? fmtElapsed(status.elapsed) + : (status.started + ? fmtElapsed(Date.now() / 1000 - status.started) : ''); if (elapsed) rows.push(E('div', { 'class': 'librespeed-muted' }, @@ -439,7 +472,15 @@ return view.extend({ else this.wasRunning = false; - if (!status.running && status.last_error) { + if (!status.running && status.last_error == 'stopped') { + /* Stopping is something the user did, not something that went + * wrong; a red banner would turn their own click into an error. */ + this.alertNode.appendChild(E('p', { 'class': 'librespeed-muted' }, [ + _('The last measurement was stopped.'), + status.last_finished ? ' · ' + fmtTime(status.last_finished) : '' + ])); + } + else if (!status.running && status.last_error) { /* Compact, and above the figures: the failure is news, the last * good result is still the substance of the page. */ this.alertNode.appendChild(E('div', { @@ -553,6 +594,11 @@ return view.extend({ this.resultHeading = E('div', { 'class': 'librespeed-muted', 'style': 'margin-top:.35em' }); this.resultNode = E('div', {}); this.recentNode = E('div', {}); + this.liveNode = E('div', { + 'role': 'status', + 'aria-live': 'polite', + 'class': 'librespeed-visually-hidden' + }); this.recentCount = E('span', { 'class': 'librespeed-muted' }); this.recentSubtitle = E('div', { 'class': 'librespeed-muted' }, [ _('last 24 hours') ]); @@ -624,10 +670,15 @@ return view.extend({ * well sit in another one, so it only formats the epoch. */ const sched = config.schedule || {}; const next = sched.next_runs || []; - const schedRows = [ [ _('Enabled'), sched.enabled ? _('Yes') : _('No') ] ]; + /* Strict, matching history.js: the backend derives this from the + * crontab and emits a real boolean, never UCI's '0'/'1'. */ + const schedRows = [ [ _('Enabled'), sched.enabled === true ? _('Yes') : _('No') ] ]; - if (sched.enabled) { - schedRows.push([ _('Interval'), sched.interval || '1d' ]); + if (sched.enabled === true) { + /* The shared table translates the six tokens the UI offers; the + * init script accepts more, so the raw token is the fallback. */ + schedRows.push([ _('Interval'), + lscommon.INTERVALS[sched.interval || '1d'] || sched.interval ]); if (next.length) schedRows.push([ _('Next run'), new Date(next[0] * 1000).toLocaleString() ]); } @@ -660,6 +711,7 @@ return view.extend({ const main = E('div', { 'class': 'cbi-section librespeed-center', 'style': 'margin:0' }, [ this.alertNode, + this.liveNode, this.statusNode, this.resultHeading, this.resultNode, diff --git a/luci-app-librespeed/po/templates/librespeed.pot b/luci-app-librespeed/po/templates/librespeed.pot index 7a0eaf331..d5a6568e2 100644 --- a/luci-app-librespeed/po/templates/librespeed.pot +++ b/luci-app-librespeed/po/templates/librespeed.pot @@ -1,17 +1,19 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:288 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:315 #: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:223 -msgid "%d measurements" -msgstr "" +msgid "%d measurement" +msgid_plural "%d measurements" +msgstr[0] "" +msgstr[1] "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:430 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:463 msgid "%s elapsed" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:124 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:136 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:123 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:135 msgid "1 year" msgstr "" @@ -19,7 +21,7 @@ msgstr "" msgid "1y" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:137 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:136 msgid "2 years" msgstr "" @@ -27,7 +29,7 @@ msgstr "" msgid "24h" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:122 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:121 msgid "30 days" msgstr "" @@ -35,7 +37,7 @@ msgstr "" msgid "30d" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:121 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:120 msgid "7 days" msgstr "" @@ -43,23 +45,23 @@ msgstr "" msgid "7d" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:123 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:135 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:122 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:134 msgid "90 days" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:157 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:156 msgid "" "A daily measurement runs at a time drawn when the schedule is applied; " "saving draws it anew." msgstr "" #: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:31 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:82 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:94 msgid "All" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:95 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:94 msgid "" "An hour window on the 24-hour clock, such as 2-5 for 02:00–05:59. A daily " "measurement runs at a random time inside it, drawn when the schedule is " @@ -67,99 +69,100 @@ msgid "" "window across midnight is not supported." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:128 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:127 msgid "Archive path" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:134 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:133 msgid "Archive retention" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:521 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:562 msgid "Completed" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:129 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:128 msgid "" "Completed days are reduced to daily minimum, average and maximum and " "appended here once a day, so measurements since the last flush may be lost " "after an unexpected power loss. Leave empty to keep no persistent history." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:405 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:389 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:426 msgid "Connecting to the test server…" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:327 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:334 msgid "Could not start the measurement: %s" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:67 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:161 msgid "Daily" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:221 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:249 msgid "Daily minimum, average and maximum." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:71 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:70 msgid "Days" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:251 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:252 msgid "Declining — the last measurement is %d%% below the 24-hour average." msgstr "" #: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:11 #: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:92 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:491 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:532 msgid "Download" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:328 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:563 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:355 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:609 msgid "Download [Mbps]" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:58 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:59 msgid "Enable" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:627 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:675 msgid "Enabled" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:651 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:702 msgid "" "Encrypted transfers, which on hardware without AES acceleration may bound " "the result." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:66 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:160 msgid "Every 12 hours" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:62 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:156 msgid "Every 15 minutes" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:63 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:157 msgid "Every 30 minutes" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:65 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:159 msgid "Every 6 hours" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:405 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:443 msgid "Export CSV" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:408 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:446 msgid "Export JSON" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:77 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:76 msgid "Friday" msgstr "" @@ -167,243 +170,263 @@ msgstr "" msgid "Grant access to LibreSpeed measurements" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:646 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:697 msgid "HTTP (forced)" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:645 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:696 msgid "HTTPS (forced)" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:147 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:159 msgid "Hide this series" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:110 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:109 #: applications/luci-app-librespeed/root/usr/share/luci/menu.d/luci-app-librespeed.json:25 msgid "History" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:116 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:215 +msgid "History keeping is switched off, so measurements are not recorded." +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:115 msgid "History stored in /tmp is lost when the router reboots." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:64 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:158 msgid "Hourly" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:94 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:93 msgid "Hours" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:327 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:372 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:25 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:509 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:641 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:354 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:410 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:26 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:550 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:692 msgid "Interface" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:61 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:630 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:62 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:680 msgid "Interval" msgstr "" #: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:14 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:499 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:540 msgid "Jitter" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:328 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:563 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:355 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:609 msgid "Jitter [ms]" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:112 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:111 msgid "Keep history" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:449 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:490 msgid "Last measurement failed" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:375 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:382 msgid "Last successful measurement" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:342 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:369 msgid "Latency" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:72 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:71 msgid "Leave empty to run every day." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:686 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:738 #: applications/luci-app-librespeed/root/usr/share/luci/menu.d/luci-app-librespeed.json:3 msgid "LibreSpeed" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:417 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:455 msgid "LibreSpeed – History" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:21 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:22 msgid "LibreSpeed – Settings" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:26 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:27 msgid "Logical interface the measurement is bound to." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:23 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:24 msgid "Measurement" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:400 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:438 msgid "Measurements" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:689 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:741 msgid "Measures the internet connection from this router." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:73 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:236 +msgid "Metric" +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:72 msgid "Monday" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:36 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:37 msgid "Must be a server id or 'auto'" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:46 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:47 msgid "Must be an http(s) URL" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:302 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:329 msgid "Next" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:632 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:683 msgid "Next run" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:627 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:675 msgid "No" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:218 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:246 msgid "No data for the selected metric." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:278 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:279 msgid "No data in the last 24 hours." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:466 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:507 msgid "No measurement yet." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:194 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:218 msgid "No measurements match the current filters." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:392 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:430 msgid "No measurements yet" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:162 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:161 msgid "" "No schedule is active. Enable it above and apply to draw the measurement " "time." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:31 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:32 msgid "" "Numeric server id from the LibreSpeed server list, or 'auto' to pick the " "closest." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:50 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:51 msgid "" "On routers without AES acceleration, TLS itself can bound the result — " "forcing plain HTTP then measures the line rather than the cipher." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:297 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:223 +msgid "Open settings" +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:324 msgid "Page %d of %d" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:115 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:114 msgid "Path" msgstr "" #: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:13 #: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:91 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:498 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:539 msgid "Ping" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:328 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:563 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:355 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:609 msgid "Ping [ms]" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:295 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:322 msgid "Previous" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:327 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:49 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:518 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:644 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:354 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:50 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:559 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:695 msgid "Protocol" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:674 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:381 +msgid "Range" +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:726 msgid "Recent history" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:120 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:119 msgid "Retention" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:687 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:739 msgid "Router speed test" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:193 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:217 msgid "Run a speed test to start building your connection history." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:59 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:60 msgid "" "Runs a measurement from cron at the chosen interval. Note that a measurement " "saturates the connection while it runs." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:78 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:77 msgid "Saturday" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:656 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:707 msgid "Schedule status" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:56 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:57 msgid "Scheduled measurements" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:327 #: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:370 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:30 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:507 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:562 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:642 +msgid "Series" +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:354 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:408 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:31 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:548 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:608 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:693 msgid "Server" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:39 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:40 msgid "Server list URL" msgstr "" @@ -411,33 +434,34 @@ msgstr "" msgid "Settings" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:147 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:159 msgid "Show this series" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:342 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:369 msgid "Speed" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:175 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:189 msgid "Stable over the selected period." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:257 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:258 msgid "Stable — averaged %d Mbps, ranging from %d to %d Mbps." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:386 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:349 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:573 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:223 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:424 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:356 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:619 msgid "Start test" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:349 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:356 msgid "Stop test" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:79 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:78 msgid "Sunday" msgstr "" @@ -445,28 +469,32 @@ msgstr "" msgid "Test" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:639 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:690 msgid "Test configuration" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:106 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:479 +msgid "The last measurement was stopped." +msgstr "" + +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:105 msgid "The window must not cross midnight" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:76 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:75 msgid "Thursday" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:327 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:562 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:354 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:608 msgid "Time" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:74 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:73 msgid "Tuesday" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:40 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:41 msgid "" "URL of a LibreSpeed server list in JSON, from a self-hosted deployment: " "either a static file such as https://example.org/servers.json or a generator " @@ -474,120 +502,121 @@ msgid "" "official list." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:649 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:700 msgid "Unencrypted transfers, so the line is measured rather than the cipher." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:150 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:149 msgid "Upcoming measurements" msgstr "" #: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:12 #: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:93 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:492 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:533 msgid "Upload" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:328 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:563 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:355 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:609 msgid "Upload [Mbps]" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:104 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:103 msgid "Use an hour (0-23) or a range like 2-5" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:254 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:255 msgid "Variable — ranged from %d to %d Mbps." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:319 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:326 msgid "View full history »" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:540 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:581 msgid "View result" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:75 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:74 msgid "Wednesday" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:652 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:703 msgid "Whichever scheme the server list provides." msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:627 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:675 msgid "Yes" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:643 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:694 msgid "automatic (nearest)" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:293 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:294 msgid "average" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:328 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:154 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:287 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/librespeed/common.js:358 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:168 msgid "avg" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:294 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:295 msgid "best" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:292 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:293 msgid "current" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:165 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:179 msgid "declining" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:516 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:557 msgid "encrypted (HTTPS)" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:53 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:54 msgid "force HTTP" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:52 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:53 msgid "force HTTPS" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:165 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:179 msgid "highly variable" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:558 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:604 msgid "last 24 hours" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:516 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:557 msgid "plain HTTP" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:360 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:367 msgid "server %s" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:51 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:647 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/settings.js:52 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:698 msgid "server default" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:165 -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:172 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:179 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:186 msgid "stable" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:122 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/history.js:134 msgid "vs previous period" msgstr "" -#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:295 +#: applications/luci-app-librespeed/htdocs/luci-static/resources/view/librespeed/test.js:296 msgid "worst" msgstr "" diff --git a/luci-app-librespeed/root/usr/share/rpcd/acl.d/luci-app-librespeed.json b/luci-app-librespeed/root/usr/share/rpcd/acl.d/luci-app-librespeed.json index 1fab601a5..1126662cd 100644 --- a/luci-app-librespeed/root/usr/share/rpcd/acl.d/luci-app-librespeed.json +++ b/luci-app-librespeed/root/usr/share/rpcd/acl.d/luci-app-librespeed.json @@ -3,15 +3,40 @@ "description": "Grant access to LibreSpeed measurements", "read": { "ubus": { - "librespeed": [ "status", "result", "history", "config" ] + "librespeed": [ + "status", + "result", + "history", + "config" + ], + "network.interface": [ + "dump" + ], + "network": [ + "get_proto_handlers" + ], + "luci-rpc": [ + "getBoardJSON", + "getNetworkDevices", + "getWirelessDevices" + ] }, - "uci": [ "librespeed" ] + "uci": [ + "librespeed", + "network", + "wireless" + ] }, "write": { "ubus": { - "librespeed": [ "start", "stop" ] + "librespeed": [ + "start", + "stop" + ] }, - "uci": [ "librespeed" ] + "uci": [ + "librespeed" + ] } } } diff --git a/luci-app-mosdns/Makefile b/luci-app-mosdns/Makefile index 806de52b9..5da24e162 100644 --- a/luci-app-mosdns/Makefile +++ b/luci-app-mosdns/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-mosdns -PKG_VERSION:=1.7.7 -PKG_RELEASE:=14 +PKG_VERSION:=1.7.8 +PKG_RELEASE:=15 LUCI_TITLE:=LuCI Support for mosdns LUCI_PKGARCH:=all diff --git a/luci-app-mosdns/po/zh_Hans/mosdns.po b/luci-app-mosdns/po/zh_Hans/mosdns.po index 02d5978ce..291c5d9a3 100644 --- a/luci-app-mosdns/po/zh_Hans/mosdns.po +++ b/luci-app-mosdns/po/zh_Hans/mosdns.po @@ -152,7 +152,7 @@ msgstr "自动保存缓存" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:323 msgid "Cache Prefetching" -msgstr "缓存预读" +msgstr "缓存预取" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/update.js:150 msgid "Check And Update" @@ -577,7 +577,7 @@ msgstr "覆盖最小 TTL 值(默认 0)" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:336 msgid "" "Minimum cache hits required since the last refresh to trigger a prefetch." -msgstr "触发预读所需的最小缓存命中次数(自上次刷新起)" +msgstr "触发预取所需的最小缓存命中次数(自上次刷新起)" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:366 msgid "" @@ -630,19 +630,19 @@ msgstr "请稍候,这可能需要一点时间..." #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:329 msgid "Prefetch Before Expire" -msgstr "预读触发阈值" +msgstr "预取触发阈值" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:335 msgid "Prefetch Min Hits" -msgstr "预读最小命中次数" +msgstr "预取最小命中次数" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:341 msgid "Prefetch Scan Interval" -msgstr "预读扫描周期" +msgstr "预取扫描周期" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:330 msgid "Prefetch when the remaining TTL is less than this value (in seconds)." -msgstr "当剩余 TTL 小于此值(秒)时触发预读" +msgstr "当剩余 TTL 小于此值(秒)时触发预取" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:301 msgid "Prevent DNS Leaks" @@ -651,7 +651,7 @@ msgstr "防止 DNS 泄漏" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:324 msgid "" "Proactively refresh hot cache entries in the background before they expire." -msgstr "在缓存到期前,后台主动预读并刷新高频访问的 DNS 记录" +msgstr "在缓存到期前,后台主动预取并刷新高频访问的 DNS 记录" #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:227 #: luci-app-mosdns/htdocs/luci-static/resources/view/mosdns/basic.js:244 diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index ab266081c..4d818855a 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.8.19 -PKG_RELEASE:=238 +PKG_RELEASE:=239 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index da04f0b65..b270d5831 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -149,7 +149,8 @@ end o = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks " .. translate("Listen Port")) o.default = 1070 -o.datatype = "port" +o.datatype = "range(1,65535)" +o.rmempty = false o:depends({ node = "", ["!reverse"] = true }) --[[ if has_singbox or has_xray then @@ -687,7 +688,16 @@ s2.addremove = true s2.extedit = api.url("socks_config", "%s") function s2.create(e, t) local uid = "socks_" .. api.gen_random_char(5) + local n = 1 + m:foreach("socks", function(s) + if s[".name"] == section then + return false + end + n = n + 1 + end) TypedSection.create(e, uid) + m:set(uid, "port", 1080 + n) + m:set(uid, "http_port", 0) luci.http.redirect(e.extedit:format(uid)) end function s2.remove(e, t) @@ -730,12 +740,6 @@ function s2.remove(e, t) TypedSection.remove(e, t) end -o = s2:option(DummyValue, "status", translate("Status")) -o.rawhtml = true -o.cfgvalue = function(t, n) - return string.format('
', n) -end - ---- Enable o = s2:option(Flag, "enabled", translate("Enable")) o.default = 1 @@ -757,26 +761,19 @@ o.cfgvalue = function(_, n) end end -local n = 1 -m:foreach("socks", function(s) - if s[".name"] == section then - return false - end - n = n + 1 -end) - -o = s2:option(Value, "port", "Socks " .. translate("Listen Port")) -o.default = n + 1080 -o.datatype = "port" -o.rmempty = false - ---[[ -if has_singbox or has_xray then - o = s2:option(Value, "http_port", "HTTP " .. translate("Listen Port")) - o.default = 0 - o.datatype = "port" +o = s2:option(DummyValue, "port_status", translate("Port Status")) +o.rawhtml = true +o.cfgvalue = function(t, n) + local socks = m:get(n, "port") or "0" + local http = m:get(n, "http_port") or "0" + return string.format([[ +
+ SOCKS: %s +
+ HTTP: %s +
+ ]], n, socks, http) end -]]-- local o_node = s.fields["node"] local o_socks = s2.fields["node"] diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua index 71b5bacc9..8d8cdf929 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua @@ -46,8 +46,9 @@ o:depends("console_auth", true) ---- Console Port o = s:option(Value, "console_port", translate("Console Port"), translate("In the browser input routing IP plus port access, such as:192.168.1.1:1188")) -o.datatype = "port" o.default = 1188 +o.datatype = "range(1,65535)" +o.rmempty = false o:depends("balancing_enable", true) o = s:option(Flag, "bind_local", translate("Haproxy Port") .. " " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) @@ -151,8 +152,8 @@ end ---- Haproxy Port o = s:option(Value, "haproxy_port", translate("Haproxy Port")) -o.datatype = "port" o.default = 1181 +o.datatype = "range(1,65535)" o.rmempty = false ---- Node Weight diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua index b9707722d..f44534476 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua @@ -63,7 +63,7 @@ end) o = s:option(Value, "port", "Socks " .. translate("Listen Port")) o.default = n + 1080 -o.datatype = "port" +o.datatype = "range(1,65535)" o.rmempty = false if has_singbox or has_xray then diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua index eb32702a1..07689c23a 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua @@ -30,6 +30,16 @@ for i, v in ipairs(user_list) do end o:depends({ auth = true }) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua index 50236d89c..f3c8730e0 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua @@ -456,9 +456,16 @@ if singbox_tags:find("with_wireguard") then o:depends({ protocol = "wireguard" }) end -o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) o.default = "0" -o:depends({ custom = false, is_endpoint = "" }) +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua index a27e518c8..98abded46 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua @@ -468,10 +468,17 @@ o = s:option(DummyValue, "gen_wireguard_key") o.template = m:template_path("/server/gen_wireguard_key") o:depends({ protocol = "wireguard" }) -o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) o.default = "0" o:depends({ custom = false }) +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" o:depends({ custom = false }) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua index 40af02de9..2672568ae 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua @@ -72,6 +72,17 @@ o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) o.default = "0" o:depends({ custom = false }) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua index f74092330..42821c00d 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua @@ -103,6 +103,17 @@ o = s:option(Flag, "udp_forward", translate("UDP Forward")) o.default = "1" o.rmempty = false +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua index 3ba460439..f1803cdbd 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua @@ -147,6 +147,17 @@ o.validate = function(self, value, t) end o:depends({ custom = false }) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall/luasrc/passwall/server_app.lua b/luci-app-passwall/luasrc/passwall/server_app.lua index cdfb22582..3c2d01977 100644 --- a/luci-app-passwall/luasrc/passwall/server_app.lua +++ b/luci-app-passwall/luasrc/passwall/server_app.lua @@ -8,16 +8,10 @@ local jsonc = api.jsonc local CONFIG = api.s_config local CONFIG_PATH = api.S_TMP_PATH -local NFT_INCLUDE_FILE = CONFIG_PATH .. "/" .. CONFIG .. ".nft" local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log" local TMP_BIN_PATH = CONFIG_PATH .. "/bin" local require_dir = "luci.passwall." -local ipt_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ipt_bin)") -local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ip6t_bin)") - -local nft_flag = api.is_finded("fw4") and "1" or "0" - local function log(...) local f, err = io.open(LOG_APP_FILE, "a") if f and err == nil then @@ -31,18 +25,6 @@ local function cmd(cmd) sys.call(cmd) end -local function ipt(arg) - if ipt_bin and #ipt_bin > 0 then - cmd(ipt_bin .. " -w " .. arg) - end -end - -local function ip6t(arg) - if ip6t_bin and #ip6t_bin > 0 then - cmd(ip6t_bin .. " -w " .. arg) - end -end - local function ln_run(s, d, command, output) if not output then output = "/dev/null" @@ -52,37 +34,6 @@ local function ln_run(s, d, command, output) return string.format("%s >%s 2>&1 &", d .. " " .. command, output) end -local function gen_include() - cmd(string.format("echo '#!/bin/sh' > %s.include", CONFIG_PATH)) - local function extract_rules(n, a) - local _ipt = ipt_bin - if n == "6" then - _ipt = ip6t_bin - end - local result = "*" .. a - result = result .. "\n" .. sys.exec(_ipt .. '-save -t ' .. a .. ' | grep "PSW-SERVER" | sed -e "s/^-A \\(INPUT\\)/-I \\1 1/"') - result = result .. "COMMIT" - return result - end - local f, err = io.open(CONFIG_PATH .. ".include", "a") - if f and err == nil then - if nft_flag == "0" then - f:write(ipt_bin .. '-save -c | grep -v "PSW-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n") - f:write(ipt_bin .. '-restore -n <<-EOT' .. "\n") - f:write(extract_rules("4", "filter") .. "\n") - f:write("EOT" .. "\n") - f:write(ip6t_bin .. '-save -c | grep -v "PSW-SERVER" | ' .. ip6t_bin .. '-restore -c' .. "\n") - f:write(ip6t_bin .. '-restore -n <<-EOT' .. "\n") - f:write(extract_rules("6", "filter") .. "\n") - f:write("EOT" .. "\n") - f:close() - else - f:write("nft -f " .. NFT_INCLUDE_FILE .. "\n") - f:close() - end - end -end - local function start() local enabled = tonumber(api.uci_get_s("@global[0]", "enable") or 0) if enabled == nil or enabled == 0 then @@ -90,18 +41,7 @@ local function start() end cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH)) cmd(string.format("touch %s", LOG_APP_FILE)) - if nft_flag == "0" then - ipt("-N PSW-SERVER") - ipt("-I INPUT -j PSW-SERVER") - ip6t("-N PSW-SERVER") - ip6t("-I INPUT -j PSW-SERVER") - else - nft_file, err = io.open(NFT_INCLUDE_FILE, "w") - nft_file:write('#!/usr/sbin/nft -f\n') - nft_file:write('add chain inet fw4 PSW-SERVER\n') - nft_file:write('flush chain inet fw4 PSW-SERVER\n') - nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n') - end + local firewall_num = 0 api.uci_foreach_s("server", function(server) local id = server[".name"] local enable = server.enable @@ -207,48 +147,38 @@ local function start() cmd(bin) end - local bind_local = server.bind_local or 0 - if bind_local and tonumber(bind_local) ~= 1 and port then - if nft_flag == "0" then - ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - ip6t(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - if udp_forward == 1 then - ipt(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - ip6t(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - end - else - nft_file:write(string.format('add rule inet fw4 PSW-SERVER meta l4proto tcp tcp dport {%s} counter accept comment "%s"\n', port, remarks)) - if udp_forward == 1 then - nft_file:write(string.format('add rule inet fw4 PSW-SERVER meta l4proto udp udp dport {%s} counter accept comment "%s"\n', port, remarks)) - end - end + local firewall_allow = server.firewall_allow + if firewall_allow == "1" then + firewall_num = firewall_num + 1 + local uid = CONFIG .. "_" .. id + uci:section("firewall", "rule", uid) + uci:set("firewall", uid, "name", uid) + uci:set("firewall", uid, "src", server.firewall_allow_src or "wan") + uci:set("firewall", uid, "dest_port", port) + uci:set("firewall", uid, "target", "ACCEPT") end end end) - if nft_flag == "1" then - nft_file:write("add rule inet fw4 PSW-SERVER return\n") - nft_file:close() - cmd("nft -f " .. NFT_INCLUDE_FILE) + if firewall_num > 0 then + api.uci_save(uci, "firewall", true, true) end - gen_include() end local function stop() cmd(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH)) - if nft_flag == "0" then - ipt("-D INPUT -j PSW-SERVER 2>/dev/null") - ipt("-F PSW-SERVER 2>/dev/null") - ipt("-X PSW-SERVER 2>/dev/null") - ip6t("-D INPUT -j PSW-SERVER 2>/dev/null") - ip6t("-F PSW-SERVER 2>/dev/null") - ip6t("-X PSW-SERVER 2>/dev/null") - else - local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done" - cmd(nft_cmd) - cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null") - cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null") + if true then + local num = 0 + uci:foreach("firewall", "rule", function(rule) + if rule[".name"]:find(CONFIG) == 1 then + num = num + 1 + uci:delete("firewall", rule[".name"]) + end + end) + if num > 0 then + api.uci_save(uci, "firewall", true, true) + end end - cmd(string.format("rm -rf %s %s %s", CONFIG_PATH, LOG_APP_FILE, CONFIG_PATH .. ".include")) + cmd(string.format("rm -rf %s %s", CONFIG_PATH, LOG_APP_FILE)) end if action then diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 8102f802e..e6cdb63f9 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -762,7 +762,7 @@ function gen_config_server(node) local inbound = { type = node.protocol, tag = "inbound", - listen = (node.bind_local == "1") and "127.0.0.1" or "::", + listen = "::", listen_port = tonumber(node.port), } diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index 71336bd94..c2037d481 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -669,7 +669,7 @@ function gen_config_server(node) -- 传入连接 inbounds = { { - listen = (node.bind_local == "1") and "127.0.0.1" or nil, + listen = "::", port = tonumber(node.port), protocol = node.protocol, settings = settings, diff --git a/luci-app-passwall/luasrc/view/passwall/global/footer.htm b/luci-app-passwall/luasrc/view/passwall/global/footer.htm index e5902cbbd..d122cedd2 100644 --- a/luci-app-passwall/luasrc/view/passwall/global/footer.htm +++ b/luci-app-passwall/luasrc/view/passwall/global/footer.htm @@ -24,16 +24,6 @@ local sid = "@global[0]" display: none !important; } } - -@media screen and (min-width: 1152px) { - #cbi-<%=appname%>-socks input[id*=".<%=appname%>."][id$=".port"], - #cbi-<%=appname%>-socks input[id*=".<%=appname%>."][id$=".http_port"] { - width: 100px !important; - min-width: unset !important; - max-width: unset !important; - text-align: center !important; - } -} ", i18n.translate("Not enabled log"))) - end -end - function get_log() -- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log") http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log")) @@ -350,7 +336,7 @@ end function index_status() local e = {} - e["global_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep 'default' | grep 'global' >/dev/null" % api.TMP_PATH) == 0 + e["global_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep '/acl/default' >/dev/null" % api.TMP_PATH) == 0 http_write_json(e) end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua index 49b5e9aa8..2c00525da 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua @@ -50,20 +50,11 @@ end o = s:option(DummyValue, "sources", translate("Source")) o.rawhtml = true -o.cfgvalue = function(t, n) - local e = '' - local v = Value.cfgvalue(t, n) or '-' - string.gsub(v, '[^' .. " " .. ']+', function(w) - local a = w - if mac_t[w] then - a = a .. ' (' .. mac_t[w].ip .. ')' - end - if #e > 0 then - e = e .. "
" - end - e = e .. a - end) - return e +o.cfgvalue = function(self, section) + local v = self.map:get(section, self.option) or {} + if type(v) == "table" then + return table.concat(v, "
") + end end i = s:option(DummyValue, "mode", translate("Mode")) @@ -71,6 +62,8 @@ i.cfgvalue = function(t, n) local v = Value.cfgvalue(t, n) or '0' if v == "1" then return translate("Proxy") + elseif v == "2" then + return translate("Proxy") .. " " .. translate("Use global config") end return translate("No Proxy") end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua index cdf7870ec..3c9cc084e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl_config.lua @@ -98,25 +98,9 @@ sources.description = "
  • " .. translate("Example:") .. "
  • " .. translate("IP range") .. ": 192.168.1.100-192.168.1.200" .. "
  • " .. translate("IPSet") .. ": ipset:lanlist" .. "
" -sources.cast = "string" for _, key in pairs(mac_t) do sources:value(key.mac, "%s (%s)" % {key.mac, key.ip}) end - -sources.cfgvalue = function(self, section) - local value - if self.tag_error[section] then - value = self:formvalue(section) - else - value = self.map:get(section, self.option) - if type(value) == "string" then - local value2 = {} - string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end) - value = value2 - end - end - return value -end sources.validate = function(self, value, t) local err = {} for _, v in ipairs(value) do @@ -155,47 +139,75 @@ sources.validate = function(self, value, t) return value end +local NODE = m:get("@global[0]", "node") o = s:option(ListValue, "mode", translate("Mode")) o:value("0", translate("No Proxy")) o:value("1", translate("Proxy")) +o:value("2", translate("Proxy") .. " " .. translate("Use global config") .. "(" .. api.get_node_name(NODE) .. ")") ----- TCP No Redir Ports -local TCP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_no_redir_ports") o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports")) -o:value("", translate("Use global config") .. "(" .. TCP_NO_REDIR_PORTS .. ")") -o:value("disable", translate("No patterns are used")) +o:value("", translate("No patterns are used")) o:value("1:65535", translate("All")) o:depends("mode", "1") +o:depends("mode", "2") o.validate = port_validate ----- UDP No Redir Ports -local UDP_NO_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_no_redir_ports") o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports")) -o:value("", translate("Use global config") .. "(" .. UDP_NO_REDIR_PORTS .. ")") -o:value("disable", translate("No patterns are used")) +o:value("", translate("No patterns are used")) o:value("1:65535", translate("All")) o:depends("mode", "1") +o:depends("mode", "2") o.validate = port_validate -o = s:option(DummyValue, "_hide_node_option", "") +o = s:option(DummyValue, "_show_tcp_redir", "") +o.template = m:template_path("/cbi/hidevalue") +o.value = "1" +o:depends({ tcp_no_redir_ports = "1:65535", ['!reverse'] = true }) + +o = s:option(DummyValue, "_show_udp_redir", "") +o.template = m:template_path("/cbi/hidevalue") +o.value = "1" +o:depends({ udp_no_redir_ports = "1:65535", ['!reverse'] = true }) + +o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports")) +o:value("1:65535", translate("All")) +o:value("22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418", translate("Common Use")) +o:value("80,443", "80,443") +o.default = o.keylist[1] +o.validate = port_validate +o:depends({ _node = "1", _show_tcp_redir = "1" }) +o:depends({ mode = "2", _show_tcp_redir = "1" }) + +o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports")) +o:value("1:65535", translate("All")) +o.default = o.keylist[1] +o.validate = port_validate +o:depends({ _node = "1", _show_udp_redir = "1" }) +o:depends({ mode = "2", _show_udp_redir = "1" }) + +o = s:option(DummyValue, "tips", " ") +o.rawhtml = true +o.cfgvalue = function(t, n) + return string.format('%s', + translate("The port settings support single ports and ranges.
Separate multiple ports with commas (,).
Example: 21,80,443,1000:2000.")) +end +o:depends("mode", "1") +o:depends("mode", "2") + +o = s:option(DummyValue, "_no_proxy", "") o.template = m:template_path("/cbi/hidevalue") o.value = "1" o:depends("mode", "0") o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" }) -if TCP_NO_REDIR_PORTS == "1:65535" and UDP_NO_REDIR_PORTS == "1:65535" then - o:depends({ tcp_no_redir_ports = "", udp_no_redir_ports = "" }) -end -local GLOBAL_ENABLED = m:get("@global[0]", "enabled") -local NODE = m:get("@global[0]", "node") +o = s:option(DummyValue, "_show_node_option", "") +o.template = m:template_path("/cbi/hidevalue") +o.value = "1" +o:depends({ mode = "1", _no_proxy = "" }) + o = s:option(ListValue, "node", "" .. translate("Node") .. "") -if GLOBAL_ENABLED == "1" and NODE then - o:value("", translate("Use global config") .. "(" .. api.get_node_name(NODE) .. ")") - o.group = {""} -else - o.group = {} -end -o:depends({ _hide_node_option = "1", ['!reverse'] = true }) +o.group = {} +o:depends("_show_node_option", "1") o.template = m:template_path("/cbi/nodes_listvalue") current_node_id = o:formvalue(arg[1]) @@ -204,9 +216,14 @@ if not current_node_id then end current_node = current_node_id and m:get(current_node_id) or {} -o = s:option(Flag, "log", translate("Enable Node Log")) +o = s:option(DummyValue, "_node", "") +o.template = m:template_path("/cbi/hidevalue") +o.value = "1" o:depends({ node = "", ['!reverse'] = true }) +o = s:option(Flag, "log", translate("Enable Node Log")) +o:depends("_node", "1") + o = s:option(ListValue, "loglevel", translate("Log Level")) o.default = "warn" o:value("debug", "Debug") @@ -218,8 +235,8 @@ o:depends("log", "1") o = s:option(DummyValue, "_acl_log", translate("Log File")) o.rawhtml = true o.cfgvalue = function(t, n) - local log_path = api.TMP_PATH .. "/acl/" .. arg[1] .. "/node.log" - local log_url = api.url("get_acl_log") .. "?id=" .. arg[1] + local log_path = api.TMP_PATH .. "/acl/" .. arg[1] .. ".log" + local log_url = api.url("get_redir_log") .. "?id=" .. arg[1] return string.format( '%s  ', log_path, @@ -229,46 +246,17 @@ o.cfgvalue = function(t, n) end o:depends("log", "1") ----- TCP Redir Ports -local TCP_REDIR_PORTS = m:get("@global_forwarding[0]", "tcp_redir_ports") -o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports")) -o:value("", translate("Use global config") .. "(" .. TCP_REDIR_PORTS .. ")") -o:value("1:65535", translate("All")) -o:value("22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418", translate("Common Use")) -o:value("80,443", "80,443") -o.validate = port_validate -o:depends({ _hide_node_option = "1", ['!reverse'] = true }) - ----- UDP Redir Ports -local UDP_REDIR_PORTS = m:get("@global_forwarding[0]", "udp_redir_ports") -o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports")) -o:value("", translate("Use global config") .. "(" .. UDP_REDIR_PORTS .. ")") -o:value("1:65535", translate("All")) -o.validate = port_validate -o:depends({ _hide_node_option = "1", ['!reverse'] = true }) - -o = s:option(DummyValue, "tips", " ") -o.rawhtml = true -o.cfgvalue = function(t, n) - return string.format('%s', - translate("The port settings support single ports and ranges.
Separate multiple ports with commas (,).
Example: 21,80,443,1000:2000.")) -end -o:depends({ _hide_node_option = "1", ['!reverse'] = true }) - -o = s:option(DummyValue, "_hide_dns_option", "") +o = s:option(DummyValue, "_show_dns_option", "") o.template = m:template_path("/cbi/hidevalue") o.value = "1" -o:depends({ node = "" }) -if GLOBAL_ENABLED == "1" and NODE then - o:depends({ node = NODE }) -end +o:depends("_hide", "1") o = s:option(ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy")) o.default = "UseIP" o:value("UseIP") o:value("UseIPv4") o:value("UseIPv6") -o:depends({ _hide_dns_option = "1", ['!reverse'] = true }) +o:depends("_show_dns_option", "1") o = s:option(ListValue, "remote_dns_protocol", translate("Remote DNS Protocol")) o:value("tcp", "TCP") @@ -279,7 +267,7 @@ if current_node.type == "sing-box" then o:value("quic", "QUIC(DoQ)") o:value("http3", "HTTP3(DoH3)") end -o:depends({ _hide_dns_option = "1", ['!reverse'] = true }) +o:depends("_show_dns_option", "1") ---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ) o = s:option(Value, "remote_dns", translate("Remote DNS")) @@ -356,12 +344,13 @@ o:depends("remote_dns_protocol", "tls") o = s:option(Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL") o.datatype = "min(1)" o.default = "30" +o:depends("_hide", "1") o = s:option(ListValue, "dns_hosts_mode", translate("Domain Override")) o:value("default", translate("Use global config")) o:value("disable", translate("No patterns are used")) o:value("custom", translate("-- custom --")) -o:depends({ _hide_dns_option = "1", ['!reverse'] = true }) +o:depends("_show_dns_option", "1") o = s:option(TextValue, "dns_hosts", translate("Domain Override")) o.rows = 5 @@ -385,6 +374,7 @@ for k, v in pairs(nodes_table) do if v.type == "sing-box" then s.fields["remote_rewrite_ttl"]:depends({ node = v.id }) end + s.fields["_show_dns_option"]:depends({ mode = "1", node = v.id }) if v.node_type == "normal" or v.protocol == "_balancing" or v.protocol == "_urltest" then --Shunt node has its own separate options. s.fields["remote_fakedns"]:depends({ node = v.id, remote_dns_protocol = "tcp" }) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua index 8bcdb52f5..e1f28d19d 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua @@ -282,8 +282,8 @@ loglevel:value("error") o = s:taboption("log", DummyValue, "_log", translate("Log File")) o.rawhtml = true o.cfgvalue = function(t, n) - local log_path = api.TMP_PATH .. "/acl/default/global.log" - local log_url = api.url("get_redir_log") .. "?id=default&name=global" + local log_path = api.TMP_PATH .. "/acl/default.log" + local log_url = api.url("get_redir_log") .. "?id=default" return string.format( '%s  ', log_path, diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua index 4d401b0ed..dd4ba20a0 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua @@ -4,7 +4,6 @@ api.set_default_cbi() local has_ss_rust = api.is_finded("sslocal") local has_singbox = api.finded_com("sing-box") local has_xray = api.finded_com("xray") -local has_hysteria2 = api.finded_com("hysteria") local ss_type = {} local trojan_type = {} local vmess_type = {} @@ -33,10 +32,6 @@ if has_xray then table.insert(hysteria2_type, s) end end -if has_hysteria2 then - local s = "hysteria2" - table.insert(hysteria2_type, s) -end m = Map() diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua index a748281d1..2bc93b48b 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua @@ -17,7 +17,6 @@ m:appendTemplate("/cbi/nodes_listvalue_com") local has_ss_rust = api.is_finded("sslocal") local has_singbox = api.finded_com("sing-box") local has_xray = api.finded_com("xray") -local has_hysteria2 = api.finded_com("hysteria") local ss_type = {} local trojan_type = {} local vmess_type = {} @@ -46,10 +45,6 @@ if has_xray then table.insert(hysteria2_type, s) end end -if has_hysteria2 then - local s = "hysteria2" - table.insert(hysteria2_type, s) -end local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do if e.node_type == "normal" then diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua index f076e3c7a..f622f076c 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua @@ -72,8 +72,7 @@ s = m:section(NamedSection, "@global_forwarding[0]", "global_forwarding", transl ---- TCP No Redir Ports o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports")) -o.default = "disable" -o:value("disable", translate("No patterns are used")) +o:value("", translate("No patterns are used")) o:value("1:65535", translate("All")) o.validate = port_validate @@ -82,23 +81,22 @@ o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"), "" .. translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") .. "") -o.default = "disable" -o:value("disable", translate("No patterns are used")) +o:value("", translate("No patterns are used")) o:value("1:65535", translate("All")) o.validate = port_validate ---- TCP Redir Ports o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports")) -o.default = "22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418" o:value("1:65535", translate("All")) o:value("22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418", translate("Common Use")) o:value("80,443", translate("Only Web")) +o.default = o.keylist[1] o.validate = port_validate ---- UDP Redir Ports o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports")) -o.default = "1:65535" o:value("1:65535", translate("All")) +o.default = o.keylist[1] o.validate = port_validate o = s:option(DummyValue, "tips", " ") diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/5_hysteria2.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/5_hysteria2.lua deleted file mode 100644 index 753c748a7..000000000 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/5_hysteria2.lua +++ /dev/null @@ -1,106 +0,0 @@ -if not api.finded_com("hysteria") then - return -end - --- [[ Hysteria2 ]] -local m, s1 = ... -local type_name = "Hysteria2" - -s1.fields["type"]:value(type_name, "Hysteria2") - -if s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "hysteria2_" - -o = s:option(Value, "address", translate("Address (Support Domain Name)")) -o:depends({ realms = false }) - -o = s:option(Value, "port", translate("Port")) -o.datatype = "port" -o:depends({ realms = false }) - -o = s:option(Value, "hop", translate("Port hopping range")) -o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") -o:depends({ realms = false }) - -o = s:option(Value, "hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5.")) -o.datatype = "or(uinteger,portrange)" -o.placeholder = "30" -o.default = "30" -o:depends({ realms = false }) - -o = s:option(Flag, "realms", translate("Realms")) -o.default = "0" - -o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") -o:depends({ realms = "1" }) -o.validate = function(self, value) - value = api.trim(value) - local realm = api.parse_realm_uri(value) - if realm then return value end - return nil, translate("Invalid Realm URL.") -end - -o = s:option(DynamicList, "realm_stun", translate("Realm STUN")) -o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } -o:depends({ realms = "1" }) - -o = s:option(Value, "auth_password", translate("Auth Password")) -o.password = true - -o = s:option(ListValue, "obfs_type", translate("Obfs Type")) -o:value("", translate("Disable")) -o:value("salamander") -o:value("gecko") - -o = s:option(Value, "obfs_password", translate("Obfs Password")) -o:depends({ obfs_type = "salamander" }) -o:depends({ obfs_type = "gecko" }) - -o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)")) -o.datatype = "uinteger" -o.placeholder = "512" -o.default = "512" -o:depends({ obfs_type = "gecko" }) - -o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) -o.datatype = "uinteger" -o.placeholder = "1200" -o.default = "1200" -o:depends({ obfs_type = "gecko" }) - -o = s:option(Flag, "fast_open", translate("Fast Open")) -o.default = "0" - -o = s:option(Value, "tls_serverName", translate("Domain")) - -o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) -o.default = "0" - -o = s:option(Value, "tls_pinSHA256", translate("PinSHA256"),translate("Certificate fingerprint")) - -o = s:option(Value, "up_mbps", translate("Max upload Mbps")) - -o = s:option(Value, "down_mbps", translate("Max download Mbps")) - -o = s:option(Value, "recv_window", translate("QUIC stream receive window")) - -o = s:option(Value, "recv_window_conn", translate("QUIC connection receive window")) - -o = s:option(Value, "idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") -o.datatype = "range(4,120)" - -o = s:option(Value, "keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") -o.datatype = "range(2,60)" - -o = s:option(Flag, "disable_mtu_discovery", translate("Disable MTU detection")) -o.default = "0" - -o = s:option(Flag, "lazy_start", translate("Lazy Start")) -o.default = "0" - -api.luci_types(s1, s) \ No newline at end of file diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua index 0d5a713bd..fd3454663 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua @@ -455,9 +455,16 @@ if singbox_tags:find("with_wireguard") then o:depends({ protocol = "wireguard" }) end -o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) o.default = "0" -o:depends({ custom = false, is_endpoint = "" }) +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/2_xray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/2_xray.lua index 892cde44e..440102ac5 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/2_xray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/2_xray.lua @@ -473,10 +473,17 @@ o = s:option(DummyValue, "gen_wireguard_key") o.template = m:template_path("/server/gen_wireguard_key") o:depends({ protocol = "wireguard" }) -o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) o.default = "0" o:depends({ custom = false }) +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" o:depends({ custom = false }) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/3_ss-rust.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/3_ss-rust.lua index 80cb56c7b..df31354e6 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/3_ss-rust.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/3_ss-rust.lua @@ -72,6 +72,17 @@ o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) o.default = "0" o:depends({ custom = false }) +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/4_ssr.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/4_ssr.lua index 958c98124..3e74f9605 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/4_ssr.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/4_ssr.lua @@ -103,6 +103,17 @@ o = s:option(Flag, "udp_forward", translate("UDP Forward")) o.default = "1" o.rmempty = false +o = s:option(Flag, "firewall_allow", translate("Firewall Allow")) +o.default = "0" +o:depends({ custom = false }) + +o = s:option(Value, "firewall_allow_src", translate("Source zone")) +o.nocreate = true +o.allowany = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" +o:depends({ custom = false, firewall_allow = true }) + o = s:option(Flag, "log", translate("Log")) o.default = "1" o.rmempty = false diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/5_hysteria2.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/5_hysteria2.lua deleted file mode 100644 index bd6c5c1de..000000000 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/5_hysteria2.lua +++ /dev/null @@ -1,143 +0,0 @@ -if not api.finded_com("hysteria") then - return -end - --- [[ Hysteria2 ]] -local m, s1 = ... -local type_name = "Hysteria2" - -s1.fields["type"]:value(type_name, "Hysteria2") - -if not s1.val["type"] then - s1.val["type"] = type_name -end - -if s1.val["type"] and s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "hysteria2_" - -o = s:option(Flag, "custom", translate("Use Custom Config")) - -o = s:option(TextValue, "custom_config", translate("Custom Config")) -o.rows = 10 -o.wrap = "off" -o:depends({ custom = true }) -o.datatype = "json" -local o_validate = o.validate -o.validate = function(self, value) - local v = o_validate(self, value) - if v then return v end - return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!") -end -o.custom_cfgvalue = function(self, section, value) - local config_str = m:get(section, "config_str") - if config_str then - return api.base64Decode(config_str) - end -end -o.custom_write = function(self, section, value) - m:set(section, "config_str", api.base64Encode(value) or "") -end - -o = s:option(Value, "port", translate("Listen Port")) -o.datatype = "port" -o:depends({ custom = false }) - -o = s:option(DynamicList, "users", translate("User")) -for i, v in ipairs(user_list) do - o:value(v[".name"], v.username) -end -o:depends({ custom = false }) - -o = s:option(Flag, "realms", translate("Realms")) -o.default = "0" -o:depends({ custom = false }) - -o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") -o:depends({ realms = true }) -o.validate = function(self, value) - value = api.trim(value) - local realm = api.parse_realm_uri(value) - if realm then return value end - return nil, translate("Invalid Realm URL.") -end - -o = s:option(DynamicList, "realm_stun", translate("Realm STUN")) -o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } -o:depends({ realms = true }) - -o = s:option(ListValue, "obfs_type", translate("Obfs Type")) -o:value("", translate("Disable")) -o:value("salamander") -o:value("gecko") -o:depends({ custom = false }) - -o = s:option(Value, "obfs_password", translate("Obfs Password")) -o:depends({ obfs_type = "salamander" }) -o:depends({ obfs_type = "gecko" }) - -o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)")) -o.datatype = "uinteger" -o.placeholder = "512" -o.default = "512" -o:depends({ obfs_type = "gecko" }) - -o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) -o.datatype = "uinteger" -o.placeholder = "1200" -o.default = "1200" -o:depends({ obfs_type = "gecko" }) - -o = s:option(Flag, "udp", translate("UDP")) -o.default = "1" -o:depends({ custom = false }) - -o = s:option(Value, "up_mbps", translate("Max upload Mbps")) -o:depends({ custom = false }) - -o = s:option(Value, "down_mbps", translate("Max download Mbps")) -o:depends({ custom = false }) - -o = s:option(Flag, "ignoreClientBandwidth", translate("ignoreClientBandwidth")) -o.default = "0" -o:depends({ custom = false }) - -o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") -o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" -if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end -o.validate = function(self, value, t) - if value and value ~= "" then - if not api.fs.access(value) then - return nil, translate("Can't find this file!") - else - return value - end - end - return nil -end -o:depends({ custom = false }) - -o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") -o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" -if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end -o.validate = function(self, value, t) - if value and value ~= "" then - if not api.fs.access(value) then - return nil, translate("Can't find this file!") - else - return value - end - end - return nil -end -o:depends({ custom = false }) - -o = s:option(Flag, "log", translate("Log")) -o.default = "1" -o.rmempty = false - -api.luci_types(s1, s) \ No newline at end of file diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index 755bd2e67..491d402fc 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -26,6 +26,7 @@ TMP_PATH = "/tmp/etc/" .. c_config CACHE_PATH = TMP_PATH .. "_tmp" S_TMP_PATH = "/tmp/etc/" .. s_config TMP_IFACE_PATH = TMP_PATH .. "/iface" +TMP_ACL_PATH = TMP_PATH .. "/acl" local lang = uci:get("luci", "main", "lang") or "auto" if lang == "auto" then @@ -201,9 +202,10 @@ function get_cache_var(key) return val end -function get_new_port() +function get_new_port(p) + if not p then p = "auto" end local cmd_format = ". /usr/share/passwall2/utils.sh ; echo -n $(get_new_port %s tcp,udp)" - return tonumber(sys.exec(string.format(cmd_format, "auto"))) + return tonumber(sys.exec(string.format(cmd_format, p))) end function exec_call(cmd) @@ -613,7 +615,7 @@ function get_valid_nodes() end end local port = e.port or e.hysteria_hop or e.hysteria2_hop - local is_realm = (e.type == "Hysteria2" or e.protocol == 'hysteria2') and e.hysteria2_realms or nil + local is_realm = e.protocol == 'hysteria2' and e.hysteria2_realms or nil if (port and e.address) or is_realm then local address = e.address if is_ip(address) or datatypes.hostname(address) or is_realm then @@ -739,7 +741,7 @@ function get_node_remarks(n) end type_name = type_name .. " " .. protocol end - if (n.type == "Hysteria2" or n.protocol == 'hysteria2') and n.hysteria2_realms then + if n.protocol == "hysteria2" and n.hysteria2_realms then type_name = type_name .. " Realm" end remarks = trim("%s:[%s]" % {type_name, n.remarks}) diff --git a/luci-app-passwall2/luasrc/passwall2/com.lua b/luci-app-passwall2/luasrc/passwall2/com.lua index aefc54595..1327e3f5b 100644 --- a/luci-app-passwall2/luasrc/passwall2/com.lua +++ b/luci-app-passwall2/luasrc/passwall2/com.lua @@ -10,21 +10,6 @@ local function gh_pre_release_url(self) return "https://github.com/Openwrt-Passwall/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-pre-release-api.json" end -_M.hysteria = { - name = "Hysteria", - repo = "HyNetwork/hysteria", - get_url = gh_release_url, - cmd_version = "version | awk '/^Version:/ {print $2}'", - remote_version_str_replace = "app/", - zipped = false, - default_path = "/usr/bin/hysteria", - match_fmt_str = "linux%%-%s$", - file_tree = { - armv6 = "arm", - armv7 = "arm" - } -} - _M["sing-box"] = { name = "Sing-Box", repo = "SagerNet/sing-box", diff --git a/luci-app-passwall2/luasrc/passwall2/server_app.lua b/luci-app-passwall2/luasrc/passwall2/server_app.lua index 783ee4f96..04005d633 100644 --- a/luci-app-passwall2/luasrc/passwall2/server_app.lua +++ b/luci-app-passwall2/luasrc/passwall2/server_app.lua @@ -8,16 +8,10 @@ local jsonc = api.jsonc local CONFIG = api.s_config local CONFIG_PATH = api.S_TMP_PATH -local NFT_INCLUDE_FILE = CONFIG_PATH .. "/" .. CONFIG .. ".nft" local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log" local TMP_BIN_PATH = CONFIG_PATH .. "/bin" local require_dir = "luci.passwall2." -local ipt_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ipt_bin)") -local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ip6t_bin)") - -local nft_flag = api.is_finded("fw4") and "1" or "0" - local function log(...) local f, err = io.open(LOG_APP_FILE, "a") if f and err == nil then @@ -31,18 +25,6 @@ local function cmd(cmd) sys.call(cmd) end -local function ipt(arg) - if ipt_bin and #ipt_bin > 0 then - cmd(ipt_bin .. " -w " .. arg) - end -end - -local function ip6t(arg) - if ip6t_bin and #ip6t_bin > 0 then - cmd(ip6t_bin .. " -w " .. arg) - end -end - local function ln_run(s, d, command, output) if not output then output = "/dev/null" @@ -52,37 +34,6 @@ local function ln_run(s, d, command, output) return string.format("%s >%s 2>&1 &", d .. " " ..command, output) end -local function gen_include() - cmd(string.format("echo '#!/bin/sh' > %s.include", CONFIG_PATH)) - local function extract_rules(n, a) - local _ipt = ipt_bin - if n == "6" then - _ipt = ip6t_bin - end - local result = "*" .. a - result = result .. "\n" .. sys.exec(_ipt .. '-save -t ' .. a .. ' | grep "PSW2-SERVER" | sed -e "s/^-A \\(INPUT\\)/-I \\1 1/"') - result = result .. "COMMIT" - return result - end - local f, err = io.open(CONFIG_PATH .. ".include", "a") - if f and err == nil then - if nft_flag == "0" then - f:write(ipt_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n") - f:write(ipt_bin .. '-restore -n <<-EOT' .. "\n") - f:write(extract_rules("4", "filter") .. "\n") - f:write("EOT" .. "\n") - f:write(ip6t_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ip6t_bin .. '-restore -c' .. "\n") - f:write(ip6t_bin .. '-restore -n <<-EOT' .. "\n") - f:write(extract_rules("6", "filter") .. "\n") - f:write("EOT" .. "\n") - f:close() - else - f:write("nft -f " .. NFT_INCLUDE_FILE .. "\n") - f:close() - end - end -end - local function start() local enabled = tonumber(api.uci_get_s("@global[0]", "enable") or 0) if enabled == nil or enabled == 0 then @@ -90,18 +41,7 @@ local function start() end cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH)) cmd(string.format("touch %s", LOG_APP_FILE)) - if nft_flag == "0" then - ipt("-N PSW2-SERVER") - ipt("-I INPUT -j PSW2-SERVER") - ip6t("-N PSW2-SERVER") - ip6t("-I INPUT -j PSW2-SERVER") - else - nft_file, err = io.open(NFT_INCLUDE_FILE, "w") - nft_file:write('#!/usr/sbin/nft -f\n') - nft_file:write('add chain inet fw4 PSW2-SERVER\n') - nft_file:write('flush chain inet fw4 PSW2-SERVER\n') - nft_file:write('insert rule inet fw4 input position 0 jump PSW2-SERVER comment "PSW2-SERVER"\n') - end + local firewall_num = 0 api.uci_foreach_s("server", function(server) local id = server[".name"] local enable = server.enable @@ -169,13 +109,6 @@ local function start() config = require(require_dir .. "util_sing-box").gen_config_server(server) end bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path) - elseif type == "Hysteria2" then - if server.custom == "1" and server.config_str then - config = jsonc.parse(api.base64Decode(server.config_str)) - else - config = require(require_dir .. "util_hysteria2").gen_config_server(server) - end - bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path) end if next(config) then @@ -191,48 +124,38 @@ local function start() cmd(bin) end - local bind_local = server.bind_local or 0 - if bind_local and tonumber(bind_local) ~= 1 and port then - if nft_flag == "0" then - ipt(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - ip6t(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - if udp_forward == 1 then - ipt(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - ip6t(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) - end - else - nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto tcp tcp dport {%s} counter accept comment "%s"\n', port, remarks)) - if udp_forward == 1 then - nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto udp udp dport {%s} counter accept comment "%s"\n', port, remarks)) - end - end + local firewall_allow = server.firewall_allow + if firewall_allow == "1" then + firewall_num = firewall_num + 1 + local uid = CONFIG .. "_" .. id + uci:section("firewall", "rule", uid) + uci:set("firewall", uid, "name", uid) + uci:set("firewall", uid, "src", server.firewall_allow_src or "wan") + uci:set("firewall", uid, "dest_port", port) + uci:set("firewall", uid, "target", "ACCEPT") end end end) - if nft_flag == "1" then - nft_file:write("add rule inet fw4 PSW2-SERVER return\n") - nft_file:close() - cmd("nft -f " .. NFT_INCLUDE_FILE) + if firewall_num > 0 then + api.uci_save(uci, "firewall", true, true) end - gen_include() end local function stop() cmd(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH)) - if nft_flag == "0" then - ipt("-D INPUT -j PSW2-SERVER 2>/dev/null") - ipt("-F PSW2-SERVER 2>/dev/null") - ipt("-X PSW2-SERVER 2>/dev/null") - ip6t("-D INPUT -j PSW2-SERVER 2>/dev/null") - ip6t("-F PSW2-SERVER 2>/dev/null") - ip6t("-X PSW2-SERVER 2>/dev/null") - else - local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW2-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done" - cmd(nft_cmd) - cmd("nft flush chain inet fw4 PSW2-SERVER 2>/dev/null") - cmd("nft delete chain inet fw4 PSW2-SERVER 2>/dev/null") + if true then + local num = 0 + uci:foreach("firewall", "rule", function(rule) + if rule[".name"]:find(CONFIG) == 1 then + num = num + 1 + uci:delete("firewall", rule[".name"]) + end + end) + if num > 0 then + api.uci_save(uci, "firewall", true, true) + end end - cmd(string.format("rm -rf %s %s %s", CONFIG_PATH, LOG_APP_FILE, CONFIG_PATH .. ".include")) + cmd(string.format("rm -rf %s %s", CONFIG_PATH, LOG_APP_FILE)) end if action then diff --git a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua b/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua deleted file mode 100644 index 2d557bcdf..000000000 --- a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua +++ /dev/null @@ -1,197 +0,0 @@ -module("luci.passwall2.util_hysteria2", package.seeall) -local api = require "luci.passwall2.api" -local jsonc = api.jsonc - -function gen_config_server(node) - local users = nil - if node.users and #node.users > 0 then - users = {} - for i, v in ipairs(node.users) do - local user = api.uci_get_s(v) or {} - if user[".type"] == "user" then - users[user.username] = user.password - end - end - if not next(users) then - users = nil - end - end - local config = { - listen = (function() - if node.realms and node.realm_url then - local url = node.realm_url:gsub("/+$", "") - if node.port then - url = url .. (url:find("?") and "&lport=" or "?lport=") .. node.port - end - return url - end - return ":" .. (node.port or "0") - end)(), - tls = { - cert = node.tls_certificateFile, - key = node.tls_keyFile, - }, - obfs = (node.obfs_type and node.obfs_password) and { - type = node.obfs_type, - [node.obfs_type] = { - password = node.obfs_password - } - } or nil, - auth = users and { - type = "userpass", - userpass = users - } or nil, - bandwidth = (node.up_mbps or node.down_mbps) and { - up = node.up_mbps and node.up_mbps .. " mbps" or nil, - down = node.down_mbps and node.down_mbps .. " mbps" or nil - } or nil, - ignoreClientBandwidth = (node.ignoreClientBandwidth == "1") and true or false, - disableUDP = (node.udp == "0") and true or false, - realm = (node.realms and node.realm_stun) and { - stunServers = node.realm_stun - } or nil - } - - if config.obfs and config.obfs.gecko then - local min = tonumber(node.obfs_MinPacketSize) or 512 - local max = tonumber(node.obfs_MaxPacketSize) or 1200 - if min <= 0 or min > max or max > 2048 then - min = 512 - max = 1200 - end - config.obfs.gecko.minPacketSize = min - config.obfs.gecko.maxPacketSize = max - end - - return config -end - -function gen_config(var) - local node_id = var["node"] - if not node_id then - print("node Cannot be empty!") - return - end - local node = api.uci_get_c(node_id) - local local_socks_address = var["local_socks_address"] or "0.0.0.0" - local local_socks_port = var["local_socks_port"] - local local_socks_username = var["local_socks_username"] - local local_socks_password = var["local_socks_password"] - local local_http_address = var["local_http_address"] or "0.0.0.0" - local local_http_port = var["local_http_port"] - local local_http_username = var["local_http_username"] - local local_http_password = var["local_http_password"] - local server_host = var["server_host"] or (node.address or ""):lower() - local server_port = var["server_port"] or node.port - - if api.is_ipv6(server_host) then - server_host = api.get_ipv6_full(server_host) - end - - local port_hop = ((server_port or "") .. "," .. (node.hysteria2_hop or "")):gsub("^[%s,]+", ""):gsub("[%s,]+$", ""):gsub(":", "-") - local server = server_host .. ":" .. (port_hop ~= "" and port_hop or "443") - - local config = { - server = (function() - if node.hysteria2_realms and node.hysteria2_realm_url then - return node.hysteria2_realm_url:gsub("/+$", "") - end - return server - end)(), - realm = (node.hysteria2_realms and node.hysteria2_realm_stun) and { - stunServers = node.hysteria2_realm_stun - } or nil, - transport = { - type = "udp", - udp = node.hysteria2_hop and (function() - local udp = {} - local t = node.hysteria2_hop_interval - if not t then return nil end - if t:find("-", 1, true) then - local min, max = t:match("^(%d+)%-(%d+)$") - min = tonumber(min) - max = tonumber(max) - if min and max then - min = (min >= 5) and min or 5 - max = (max >= min) and max or min - udp.minHopInterval = min .. "s" - udp.maxHopInterval = max .. "s" - return udp - end - end - t = tonumber((t or "30"):match("^%d+")) - t = (t and t >= 5) and t or 30 - udp.hopInterval = t .. "s" - return udp - end)() or nil - }, - obfs = (node.hysteria2_obfs_type and node.hysteria2_obfs_password) and { - type = node.hysteria2_obfs_type, - [node.hysteria2_obfs_type] = { - password = node.hysteria2_obfs_password - } - } or nil, - auth = node.hysteria2_auth_password, - tls = { - sni = node.tls_serverName, - insecure = (node.tls_allowInsecure == "1") and true or false, - pinSHA256 = (node.tls_pinSHA256) and node.tls_pinSHA256 or nil, - }, - quic = { - initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil, - initConnReceiveWindow = (node.hysteria2_recv_window_conn) and tonumber(node.hysteria2_recv_window_conn) or nil, - maxIdleTimeout = (function(t) - t = tonumber(tostring(t or "30"):match("^%d+")) - return (t and t >= 4 and t <= 120) and t .. "s" or "30s" - end)(node.hysteria2_idle_timeout), - keepAlivePeriod = (function(t) - t = tonumber(tostring(t or "0"):match("^%d+")) - return (t and t >= 2 and t <= 60) and t .. "s" or nil - end)(node.hysteria2_keep_alive_period), - disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false, - }, - bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { - up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil, - down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil - } or nil, - fast_open = (node.fast_open == "1") and true or false, - lazy = (node.hysteria2_lazy_start == "1") and true or false, - socks5 = (local_socks_address and local_socks_port) and { - listen = local_socks_address .. ":" .. local_socks_port, - username = (local_socks_username and local_socks_password) and local_socks_username or nil, - password = (local_socks_username and local_socks_password) and local_socks_password or nil, - disableUDP = false, - } or nil, - http = (local_http_address and local_http_port) and { - listen = local_http_address .. ":" .. local_http_port, - username = (local_http_username and local_http_password) and local_http_username or nil, - password = (local_http_username and local_http_password) and local_http_password or nil, - } or nil - } - - if config.obfs and config.obfs.gecko then - local min = tonumber(node.hysteria2_obfs_MinPacketSize) or 512 - local max = tonumber(node.hysteria2_obfs_MaxPacketSize) or 1200 - if min <= 0 or min > max or max > 2048 then - min = 512 - max = 1200 - end - config.obfs.gecko.minPacketSize = min - config.obfs.gecko.maxPacketSize = max - end - - return jsonc.stringify(config, 1) -end - -_G.gen_config = gen_config - -if arg[1] then - local func =_G[arg[1]] - if func then - local var = nil - if arg[2] then - var = jsonc.parse(arg[2]) - end - print(func(var)) - end -end diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index ce771cc77..7c3aad6f1 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -784,7 +784,7 @@ function gen_config_server(node) local inbound = { type = node.protocol, tag = "inbound", - listen = (node.bind_local == "1") and "127.0.0.1" or "::", + listen = "::", listen_port = tonumber(node.port), } diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 9fcee2e16..a5312e685 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -675,7 +675,7 @@ function gen_config_server(node) }, inbounds = { { - listen = (node.bind_local == "1") and "127.0.0.1" or nil, + listen = "::", port = tonumber(node.port), protocol = node.protocol, settings = settings, diff --git a/luci-app-passwall2/luasrc/view/passwall2/global/footer.htm b/luci-app-passwall2/luasrc/view/passwall2/global/footer.htm index a279f3b7e..0312b6b38 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/global/footer.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/global/footer.htm @@ -133,7 +133,7 @@ local sid = "@global[0]" const m = cbid.match(/\.node$/); if (m) { - html += '?id=default&name=' + 'global' + '\', \'_blank\')"><%:Log%>'; + html += '?id=default' + '\', \'_blank\')"><%:Log%>'; } html = '
' + html + '
' diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm index 7d18cac35..6211a78f7 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm @@ -7,13 +7,12 @@ local map = self.map local has_ss_rust = api.is_finded("sslocal") local has_singbox = api.finded_com("sing-box") local has_xray = api.finded_com("xray") -local has_hysteria2 = api.finded_com("hysteria") local ss_type = api.get_core("ss_type", {{has_ss_rust,"shadowsocks-rust"},{has_singbox,"sing-box"},{has_xray,"xray"}}) local trojan_type = api.get_core("trojan_type", {{has_singbox,"sing-box"},{has_xray,"xray"}}) local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}}) local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}}) -local hysteria2_type = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}, {has_xray,"xray"}}) +local hysteria2_type = api.get_core("hysteria2_type", {{has_singbox,"sing-box"}, {has_xray,"xray"}}) local current_node = map:get(section) -%> @@ -21,7 +20,6 @@ local current_node = map:get(section)