From cf5ae27e9145ec0e73cf061c1bf0c66487500d6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 24 Aug 2026 23:23:52 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=A4=20Sync=202026-08-24=2023:23:52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- golang/golang-values.mk | 2 +- golang/golang-version.mk | 4 +- golang/{golang1.26 => golang1.27}/Makefile | 8 +- .../test-version.sh | 0 golang/{golang1.26 => golang1.27}/test.sh | 0 luci-app-ddns/po/cs/ddns.po | 6 +- luci-app-ddns/po/es/ddns.po | 10 +- luci-app-ddns/po/lo/ddns.po | 10 +- luci-app-ddns/po/pl/ddns.po | 8 +- luci-app-ddns/po/ru/ddns.po | 13 +- luci-app-ddns/po/sv/ddns.po | 274 ++- .../luci-static/resources/dockerman/api.js | 48 +- .../luci-static/resources/dockerman/common.js | 3 +- .../resources/view/dockerman/container.js | 5 +- luci-app-dockerman/po/bn_BD/dockerman.po | 8 +- luci-app-dockerman/po/cs/dockerman.po | 12 +- luci-app-dockerman/po/es/dockerman.po | 8 +- luci-app-dockerman/po/ga/dockerman.po | 10 +- luci-app-dockerman/po/ja/dockerman.po | 6 +- luci-app-dockerman/po/ko/dockerman.po | 6 +- luci-app-dockerman/po/lo/dockerman.po | 38 +- luci-app-dockerman/po/ru/dockerman.po | 21 +- luci-app-dockerman/po/sv/dockerman.po | 723 +++--- luci-app-dockerman/po/tr/dockerman.po | 14 +- luci-app-dockerman/po/zh_Hans/dockerman.po | 12 +- .../root/usr/share/rpcd/ucode/docker_rpc.uc | 19 + luci-app-ksmbd/po/lo/ksmbd.po | 8 +- luci-app-ksmbd/po/sv/ksmbd.po | 43 +- luci-app-passwall/Makefile | 2 +- .../model/cbi/passwall/client/node_list.lua | 36 +- .../cbi/passwall/client/node_subscribe.lua | 12 +- .../luasrc/passwall/util_xray.lua | 8 +- .../view/passwall/cbi/nodes_value_com.htm | 2 +- .../luasrc/view/passwall/global/faq.htm | 58 +- .../view/passwall/node_list/node_list.htm | 6 +- .../view/passwall/rule/shunt_rule_list.htm | 4 +- .../view/passwall/rule_list/geoview.htm | 2 +- .../root/etc/uci-defaults/luci-app-passwall | 2 +- .../etc/uci-defaults/luci-app-passwall_server | 2 +- .../usr/share/passwall/helper_dnsmasq.lua | 1628 +++++++------- luci-app-passwall2/Makefile | 2 +- .../model/cbi/passwall2/client/geoview.lua | 12 +- .../model/cbi/passwall2/client/node_list.lua | 36 +- .../cbi/passwall2/client/node_subscribe.lua | 8 +- .../cbi/passwall2/client/type/1_sing-box.lua | 1952 ++++++++--------- .../cbi/passwall2/client/type/2_xray.lua | 1802 +++++++-------- .../cbi/passwall2/client/type/3_ss-rust.lua | 134 +- .../model/cbi/passwall2/client/type/4_ssr.lua | 130 +- .../cbi/passwall2/server/server_config.lua | 2 +- .../cbi/passwall2/server/type/1_sing-box.lua | 1066 ++++----- .../cbi/passwall2/server/type/2_xray.lua | 1102 +++++----- .../cbi/passwall2/server/type/3_ss-rust.lua | 178 +- .../model/cbi/passwall2/server/type/4_ssr.lua | 240 +- luci-app-passwall2/luasrc/passwall2/api.lua | 16 +- .../luasrc/passwall2/util_xray.lua | 8 +- .../view/passwall2/cbi/nodes_value_com.htm | 2 +- .../luasrc/view/passwall2/global/faq.htm | 56 +- .../view/passwall2/node_list/node_list.htm | 10 +- .../luasrc/view/passwall2/rule/geoview.htm | 2 +- .../view/passwall2/rule/shunt_rule_list.htm | 4 +- .../root/etc/uci-defaults/luci-app-passwall2 | 2 +- .../uci-defaults/luci-app-passwall2_server | 2 +- .../usr/share/passwall2/helper_dnsmasq.lua | 742 +++---- .../root/usr/share/passwall2/i18n.lua | 12 +- .../root/usr/share/passwall2/nftables.sh | 2 +- luci-app-samba4/po/lo/samba4.po | 8 +- luci-app-samba4/po/sv/samba4.po | 68 +- luci-app-smartdns/Makefile | 2 +- luci-app-smartdns/po/cs/smartdns.po | 6 +- luci-app-smartdns/po/hu/smartdns.po | 8 +- luci-app-smartdns/po/lo/smartdns.po | 8 +- luci-app-smartdns/po/ru/smartdns.po | 6 +- luci-app-smartdns/po/sv/smartdns.po | 317 +-- luci-app-transmission/po/sv/transmission.po | 122 +- luci-app-watchcat/po/lo/watchcat.po | 8 +- luci-app-watchcat/po/sv/watchcat.po | 43 +- luci-app-watchcat/po/tr/watchcat.po | 10 +- luci-theme-footstrap/Makefile | 2 +- .../htdocs/luci-static/resources/fs-fit.js | 240 +- .../styles/theme/25-progressbar.css | 29 + .../styles/theme/30-tables.css | 11 +- luci-theme-footstrap/styles/theme/45-misc.css | 17 +- 82 files changed, 5971 insertions(+), 5537 deletions(-) rename golang/{golang1.26 => golang1.27}/Makefile (94%) rename golang/{golang1.26 => golang1.27}/test-version.sh (100%) rename golang/{golang1.26 => golang1.27}/test.sh (100%) diff --git a/golang/golang-values.mk b/golang/golang-values.mk index 4d2c61e6..1329a7b2 100644 --- a/golang/golang-values.mk +++ b/golang/golang-values.mk @@ -124,7 +124,7 @@ unexport \ GOBOOTSTRAP_TOOLEXEC -GO_DEFAULT_VERSION:=1.26 +GO_DEFAULT_VERSION:=1.27 GO_HOST_VERSION:=$(patsubst golang%/host,%,$(filter golang%/host,$(PKG_BUILD_DEPENDS))) ifeq ($(GO_HOST_VERSION),) GO_HOST_VERSION:=$(GO_DEFAULT_VERSION) diff --git a/golang/golang-version.mk b/golang/golang-version.mk index 335969a6..6bcdd2c9 100644 --- a/golang/golang-version.mk +++ b/golang/golang-version.mk @@ -22,7 +22,7 @@ PKG_UNPACK:=$(HOST_TAR) -C "$(PKG_BUILD_DIR)" --strip-components=1 -xzf "$(DL_DI HOST_UNPACK:=$(HOST_TAR) -C "$(HOST_BUILD_DIR)" --strip-components=1 -xzf "$(DL_DIR)/$(PKG_SOURCE)" # Don't strip ELF executables in test data -RSTRIP:=$(subst $(SCRIPT_DIR)/rstrip.sh,go-strip-helper $(SCRIPT_DIR)/rstrip.sh,$(RSTRIP)) +RSTRIP:=$(subst $(SCRIPT_DIR)/rstrip.sh,../go-strip-helper $(SCRIPT_DIR)/rstrip.sh,$(RSTRIP)) ifeq ($(GO_TARGET_SPECTRE_SUPPORTED),1) PKG_CONFIG_DEPENDS+=CONFIG_GOLANG_SPECTRE @@ -295,5 +295,5 @@ endef # src/debug contains ELF executables as test data and they reference these # libraries we need to call this to pass CheckDependencies in package-pack.mk define Package/$(PKG_NAME)-tests/extra_provides - echo 'libc.so.6' libstdc++.so.6' libtiff.so.6' | tr ' ' '\n' + echo 'libc.so.6 libstdc++.so.6 libtiff.so.6' | tr ' ' '\n' endef diff --git a/golang/golang1.26/Makefile b/golang/golang1.27/Makefile similarity index 94% rename from golang/golang1.26/Makefile rename to golang/golang1.27/Makefile index 761dea9e..a182a277 100644 --- a/golang/golang1.26/Makefile +++ b/golang/golang1.27/Makefile @@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk -PKG_NAME:=golang1.26 -GO_VERSION_MAJOR_MINOR:=1.26 -GO_VERSION_PATCH:=6 +PKG_NAME:=golang1.27 +GO_VERSION_MAJOR_MINOR:=1.27 +GO_VERSION_PATCH:=0 GO_VERSION_RC:= GO_BOOTSTRAP_VERSION:=bootstrap -PKG_HASH:=a0721c54c688901448d77ad9b3ec7ea7c474730755ff891382e92ecb93ff2cb1 +PKG_HASH:=7002403d7cc44529ef6d26f69a44818263395ead7c16c05a5808ae047ebeb0e5 PKG_VERSION:=$(GO_VERSION_MAJOR_MINOR)$(if $(GO_VERSION_RC),.0)$(if $(GO_VERSION_PATCH),.$(GO_VERSION_PATCH)) PKG_FILE_VERSION:=$(GO_VERSION_MAJOR_MINOR)$(if $(GO_VERSION_RC),rc$(GO_VERSION_RC))$(if $(GO_VERSION_PATCH),.$(GO_VERSION_PATCH)) diff --git a/golang/golang1.26/test-version.sh b/golang/golang1.27/test-version.sh similarity index 100% rename from golang/golang1.26/test-version.sh rename to golang/golang1.27/test-version.sh diff --git a/golang/golang1.26/test.sh b/golang/golang1.27/test.sh similarity index 100% rename from golang/golang1.26/test.sh rename to golang/golang1.27/test.sh diff --git a/luci-app-ddns/po/cs/ddns.po b/luci-app-ddns/po/cs/ddns.po index b294d07c..614ed31f 100644 --- a/luci-app-ddns/po/cs/ddns.po +++ b/luci-app-ddns/po/cs/ddns.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: luci-app-ddns 2.4.0-1\n" "POT-Creation-Date: 2016-01-30 11:07+0100\n" -"PO-Revision-Date: 2026-05-14 10:34+0000\n" +"PO-Revision-Date: 2026-08-13 09:27+0000\n" "Last-Translator: Pavel Borecki \n" "Language-Team: Czech \n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n" -"X-Generator: Weblate 2026.5.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -606,7 +606,7 @@ msgstr "Registrovaná IP adresa" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:567 msgid "Reload" -msgstr "Načíst znovu" +msgstr "Znovunačíst" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:550 msgid "Reload this service" diff --git a/luci-app-ddns/po/es/ddns.po b/luci-app-ddns/po/es/ddns.po index 0976c3ff..c7b1d7ff 100644 --- a/luci-app-ddns/po/es/ddns.po +++ b/luci-app-ddns/po/es/ddns.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: luci-app-ddns 2.4.0-1\n" "POT-Creation-Date: 2016-01-30 11:07+0100\n" -"PO-Revision-Date: 2026-06-26 11:18+0000\n" -"Last-Translator: apemay \n" +"PO-Revision-Date: 2026-08-14 17:53+0000\n" +"Last-Translator: Franco Castillo \n" "Language-Team: Spanish \n" "Language: es\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2026.7.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -646,7 +646,7 @@ msgstr "Ejecutar una vez" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1120 msgid "Running" -msgstr "Ejecutando" +msgstr "Ejecutándose" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:813 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:870 @@ -839,7 +839,7 @@ msgstr "Verificar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:965 msgid "Warning" -msgstr "Aviso" +msgstr "Advertencia" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:504 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:630 diff --git a/luci-app-ddns/po/lo/ddns.po b/luci-app-ddns/po/lo/ddns.po index f16c5b15..d505044a 100644 --- a/luci-app-ddns/po/lo/ddns.po +++ b/luci-app-ddns/po/lo/ddns.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-05-02 19:29+0000\n" -"Last-Translator: BoneNI \n" +"PO-Revision-Date: 2026-08-15 13:06+0000\n" +"Last-Translator: Bone NI \n" "Language-Team: Lao \n" "Language: lo\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.17.1\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -367,7 +367,7 @@ msgstr "ຕິດຕັ້ງບໍລິການ" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:812 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:862 msgid "Interface" -msgstr "ອິນເຕີເຟດ" +msgstr "ອິນເຕີເຟສ" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1006 msgid "Interval to force an update at the DDNS Provider" @@ -487,7 +487,7 @@ msgstr "IPs ທີ່ບໍ່ແມ່ນສາທາລະນະ ແລະ ຖ #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1121 msgid "Not Running" -msgstr "ບໍ່ໄດ້ເຮັດວຽກ" +msgstr "ບໍ່ເຮັດວຽກ" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:964 msgid "Notice" diff --git a/luci-app-ddns/po/pl/ddns.po b/luci-app-ddns/po/pl/ddns.po index dd44cbc8..34d392e5 100644 --- a/luci-app-ddns/po/pl/ddns.po +++ b/luci-app-ddns/po/pl/ddns.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: luci-app-ddns 2.4.0-1\n" "POT-Creation-Date: 2016-01-30 11:07+0100\n" -"PO-Revision-Date: 2026-02-14 00:51+0000\n" -"Last-Translator: Piotr Kołtun \n" +"PO-Revision-Date: 2026-08-03 04:18+0000\n" +"Last-Translator: Matthaiks \n" "Language-Team: Polish \n" "Language: pl\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 5.16-dev\n" +"X-Generator: Weblate 2026.8.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." @@ -509,7 +509,7 @@ msgstr "Niepubliczne i domyślnie zablokowane IP" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1121 msgid "Not Running" -msgstr "Nie działa" +msgstr "Nieuruchomione" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:964 msgid "Notice" diff --git a/luci-app-ddns/po/ru/ddns.po b/luci-app-ddns/po/ru/ddns.po index 080107f0..c61dfc75 100644 --- a/luci-app-ddns/po/ru/ddns.po +++ b/luci-app-ddns/po/ru/ddns.po @@ -2,8 +2,9 @@ msgid "" msgstr "" "Project-Id-Version: LuCI: ddns\n" "POT-Creation-Date: 2017-10-17 21:00+0300\n" -"PO-Revision-Date: 2026-07-03 15:28+0000\n" -"Last-Translator: chigelo \n" +"PO-Revision-Date: 2026-08-14 12:12+0000\n" +"Last-Translator: SnIPeRSnIPeR " +"\n" "Language-Team: Russian \n" "Language: ru\n" @@ -12,7 +13,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 2026.7.1.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" "Project-Info: Это технический перевод, не дословный. Главное-удобный русский " "интерфейс, все проверялось в графическом режиме, совместим с другими apps\n" @@ -205,7 +206,7 @@ msgstr "Версия DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:545 msgid "Edit" -msgstr "Редактировать" +msgstr "Изменить" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:783 msgid "Enable secure communication with DDNS provider" @@ -519,7 +520,7 @@ msgstr "Непубличные и заблокированные по умолч #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1121 msgid "Not Running" -msgstr "Не работает" +msgstr "Не запущено" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:964 msgid "Notice" @@ -709,7 +710,7 @@ msgstr "Состояние" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1117 msgid "Status" -msgstr "Статус" +msgstr "Состояние" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:410 msgid "Status directory" diff --git a/luci-app-ddns/po/sv/ddns.po b/luci-app-ddns/po/sv/ddns.po index f7723f50..0c0c9460 100644 --- a/luci-app-ddns/po/sv/ddns.po +++ b/luci-app-ddns/po/sv/ddns.po @@ -1,22 +1,22 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-03-22 17:10+0000\n" -"Last-Translator: Kristoffer Grundström \n" +"PO-Revision-Date: 2026-08-13 09:16+0000\n" +"Last-Translator: Jonatan Nyberg \n" "Language-Team: Swedish \n" "Language: sv\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.17-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423 msgid "\"../\" not allowed in path for Security Reason." -msgstr "\"../\" ej tillåten i sökväg av säkerhetsskäl." +msgstr "”../” är inte tillåtet i sökvägen av säkerhetsskäl." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:763 msgid "A.k.a. the TOKEN at e.g. afraid.org" -msgstr "" +msgstr "Även kallad TOKEN, exempelvis hos afraid.org" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:458 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:526 @@ -25,7 +25,7 @@ msgstr "Lägg till nya tjänster..." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:607 msgid "Advanced Settings" -msgstr "Avancerade inställningar" +msgstr "avancerade inställningar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:392 msgid "Allow non-public IPs" @@ -37,34 +37,38 @@ msgstr "Grundläggande inställningar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:903 msgid "Bind Network" -msgstr "Bind samman nätverk" +msgstr "Bind till nätverk" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:332 msgid "Binding to a specific network not supported" -msgstr "Att binda samman med ett specifikt nätverk stöds inte" +msgstr "Bindning till ett specifikt nätverk stöds inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:361 msgid "" "BusyBox's nslookup and hostip do not support TCP instead of the default UDP " "when sending requests to the DNS server!" msgstr "" +"BusyBox nslookup och hostip stöder inte TCP i stället för " +"standardprotokollet UDP när begäranden skickas till DNS-servern!" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:372 msgid "" "BusyBox's nslookup in the current compiled version does not handle given DNS " "Servers correctly!" msgstr "" +"BusyBox nslookup i den aktuella kompilerade versionen hanterar inte angivna " +"DNS-servrar korrekt!" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:443 msgid "CA cert bundle file" -msgstr "Sökväg till CA-certifikat" +msgstr "Fil med CA-certifikatpaket" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:444 msgid "" "CA certificate bundle file that will be used to download services data. Set " "IGNORE to skip certificate validation." msgstr "" -"Sökvägen till CA-certifikaten som används för att ladda ner tjänstdata. Välj " +"Fil med CA-certifikatpaket som används för att hämta tjänstedata. Ange " "IGNORE för att hoppa över certifikatvalidering." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:532 @@ -77,7 +81,7 @@ msgstr "Kontrollintervall" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:996 msgid "Check Unit" -msgstr "Kontrollera enhet" +msgstr "Enhet för kontrollintervall" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:27 msgid "Configuration" @@ -90,11 +94,11 @@ msgstr "Konfigurationsfel" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:417 msgid "Contains Log files for each running section." -msgstr "Mappen innehåller Logg-filer för varje del som körs." +msgstr "Innehåller loggfiler för varje aktiv sektion." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:411 msgid "Contains PID and other status information for each running section." -msgstr "" +msgstr "Innehåller PID och annan statusinformation för varje aktiv sektion." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:536 msgid "Create service" @@ -108,16 +112,16 @@ msgstr "Nuvarande inställning:" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:276 msgid "Currently DDNS updates are not started at boot or on interface events." msgstr "" +"För närvarande startas DDNS-uppdateringar inte vid uppstart eller " +"gränssnittshändelser." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:727 msgid "Custom update script for updating your DDNS Provider." -msgstr "" -"Skräddarsytt uppdateringsskript att använda vid uppdatering av din DDNS-" -"leverantör." +msgstr "Anpassat uppdateringsskript för att uppdatera din DDNS-leverantör." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:708 msgid "Custom update-URL" -msgstr "Anpassad webbadress för uppdatering" +msgstr "Anpassad uppdaterings-URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:726 msgid "Custom update-script" @@ -130,12 +134,12 @@ msgstr "DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:207 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:279 msgid "DDNS Autostart disabled" -msgstr "DDNS autostart inaktiverad" +msgstr "Automatisk start av DDNS är inaktiverad" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:206 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:279 msgid "DDNS Autostart enabled" -msgstr "DDNS autostart aktiverad" +msgstr "Automatisk start av DDNS är aktiverad" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:512 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:640 @@ -144,11 +148,11 @@ msgstr "DDNS-tjänstleverantör" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:577 msgid "DDns Service" -msgstr "DDns-tjänst" +msgstr "DDNS-tjänst" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:360 msgid "DNS requests via TCP not supported" -msgstr "DNS-förfrågningar via TCP stöds inte" +msgstr "DNS-begäranden via TCP stöds inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:924 msgid "DNS-Server" @@ -160,15 +164,15 @@ msgstr "Datumformat" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:853 msgid "Defines the Web page to read systems IP-Address from." -msgstr "" +msgstr "Anger webbsidan som systemets IP-adress ska läsas från." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:863 msgid "Defines the interface to read systems IP-Address from" -msgstr "" +msgstr "Anger gränssnittet som systemets IP-adress ska läsas från" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:845 msgid "Defines the network to read systems IP-Address from" -msgstr "" +msgstr "Anger nätverket som systemets IP-adress ska läsas från" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:18 msgid "Disabled" @@ -194,7 +198,7 @@ msgstr "Redigera" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:783 msgid "Enable secure communication with DDNS provider" -msgstr "Aktivera säker kommunikation med DDNS-leverantör" +msgstr "Aktivera säker kommunikation med DDNS-leverantören" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:612 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1140 @@ -207,24 +211,24 @@ msgstr "Fel" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1040 msgid "Error Max Retry Counter" -msgstr "" +msgstr "Högsta antal omförsök vid fel" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1050 msgid "Error Retry Interval" -msgstr "" +msgstr "Intervall för omförsök vid fel" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:877 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:887 msgid "Event Network" -msgstr "" +msgstr "Händelsenätverk" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:855 msgid "Example for IPv4" -msgstr "" +msgstr "Exempel för IPv4" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:857 msgid "Example for IPv6" -msgstr "" +msgstr "Exempel för IPv6" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:975 msgid "File" @@ -232,11 +236,11 @@ msgstr "Fil" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:711 msgid "Follow instructions found on their WEB page." -msgstr "" +msgstr "Följ anvisningarna på leverantörens webbsida." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:403 msgid "For supported codes look here" -msgstr "" +msgstr "Se här för koder som stöds" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:915 msgid "Force IP Version" @@ -244,15 +248,15 @@ msgstr "Tvinga IP-version" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1005 msgid "Force Interval" -msgstr "" +msgstr "Tvingat intervall" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:936 msgid "Force TCP on DNS" -msgstr "" +msgstr "Tvinga TCP för DNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1031 msgid "Force Unit" -msgstr "" +msgstr "Enhet för tvingad uppdatering" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:948 msgid "Format" @@ -267,8 +271,8 @@ msgid "" "GNU Wget will use the IP of given network, cURL will use the physical " "interface." msgstr "" -"GNU Wget kommer att använda IP-adressen för det angivna nätverket, cURL " -"kommer att använda det fysiska gränssnittet." +"GNU Wget använder IP-adressen för det angivna nätverket, medan cURL använder " +"det fysiska gränssnittet." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:265 msgid "Global Settings" @@ -276,7 +280,7 @@ msgstr "Globala inställningar" #: applications/luci-app-ddns/root/usr/share/rpcd/acl.d/luci-app-ddns.json:3 msgid "Grant access to ddns procedures" -msgstr "" +msgstr "Ge åtkomst till DDNS-procedurer" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:320 msgid "HTTPS not supported" @@ -284,16 +288,16 @@ msgstr "HTTPS stöds inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:622 msgid "Hostname/FQDN to validate, whether an IP update is necessary" -msgstr "" +msgstr "Värdnamn/FQDN som används för att avgöra om en IP-uppdatering behövs" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:806 msgid "IP address source" -msgstr "IP-adressens källa" +msgstr "Källa för IP-adress" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:503 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:629 msgid "IP address version" -msgstr "Version för IP-adress" +msgstr "IP-adressversion" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:506 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:634 @@ -302,11 +306,11 @@ msgstr "IPv4-adress" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:950 msgid "IPv6 address must be given in square brackets" -msgstr "" +msgstr "IPv6-adressen måste anges inom hakparenteser" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:311 msgid "IPv6 is not supported by this system" -msgstr "" +msgstr "IPv6 stöds inte av det här systemet" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:310 msgid "IPv6 not supported" @@ -322,21 +326,23 @@ msgid "" "If Wget and cURL package are installed, Wget is used for communication by " "default." msgstr "" +"Om paketen Wget och cURL är installerade används Wget som standard för " +"kommunikationen." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:613 msgid "If this service section is disabled it will not be started." -msgstr "" +msgstr "Om den här tjänstesektionen är inaktiverad startas den inte." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:384 msgid "If using secure communication you should verify server certificates!" -msgstr "" +msgstr "Om säker kommunikation används bör servercertifikaten verifieras!" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:325 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:341 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:353 msgid "" "In some versions cURL/libcurl in OpenWrt is compiled without proxy support." -msgstr "" +msgstr "I vissa OpenWrt-versioner är cURL/libcurl kompilerat utan proxystöd." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:963 msgid "Info" @@ -351,16 +357,20 @@ msgid "" "Install 'bind-host' or 'knot-host' or 'drill' package if you know you need " "TCP for DNS requests." msgstr "" +"Installera paketet ”bind-host”, ”knot-host” eller ”drill” om du vet att du " +"behöver TCP för DNS-begäranden." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:386 msgid "" "Install 'ca-certificates' package or needed certificates by hand into /etc/" "ssl/certs default directory" msgstr "" +"Installera paketet ”ca-certificates” eller lägg manuellt de certifikat som " +"behövs i standardkatalogen /etc/ssl/certs" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:670 msgid "Install Service" -msgstr "" +msgstr "Installera tjänst" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:812 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:862 @@ -369,15 +379,16 @@ msgstr "Gränssnitt" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1006 msgid "Interval to force an update at the DDNS Provider" -msgstr "" +msgstr "Intervall för att tvinga en uppdatering hos DDNS-leverantören" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1032 msgid "Interval unit for forced updates sent to DDNS Provider." msgstr "" +"Intervallenhet för tvingade uppdateringar som skickas till DDNS-leverantören." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:997 msgid "Interval unit to check for changed IP" -msgstr "" +msgstr "Intervallenhet för kontroll av ändrad IP-adress" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1145 msgid "Last Update" @@ -385,11 +396,11 @@ msgstr "Senaste uppdateringen" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:609 msgid "Log File Viewer" -msgstr "Visare av loggfil" +msgstr "Loggfilvisare" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:416 msgid "Log directory" -msgstr "" +msgstr "Loggkatalog" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:428 msgid "Log length" @@ -407,11 +418,13 @@ msgstr "Logga till syslog" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1130 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:29 msgid "Lookup Hostname" -msgstr "Kolla upp värdnamn" +msgstr "Slå upp värdnamn" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:807 msgid "Method used to determine the system IP-Address to send in updates" msgstr "" +"Metod som används för att fastställa systemets IP-adress som ska skickas i " +"uppdateringar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:491 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1124 @@ -423,16 +436,20 @@ msgid "" "Neither GNU Wget with SSL nor cURL is installed to select a network to use " "for communication." msgstr "" +"Varken GNU Wget med SSL eller cURL är installerat, vilket krävs för att " +"välja ett nätverk för kommunikationen." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:321 msgid "" "Neither GNU Wget with SSL nor cURL is installed to support secure updates " "via HTTPS protocol." msgstr "" +"Varken GNU Wget med SSL eller cURL är installerat för att stödja säkra " +"uppdateringar via HTTPS-protokollet." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:615 msgid "Neither from LuCI interface nor from console." -msgstr "" +msgstr "Varken från LuCI-gränssnittet eller från konsolen." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:810 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:844 @@ -444,7 +461,7 @@ msgstr "Nätverk" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:888 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:906 msgid "Network on which the ddns-updater scripts will be started" -msgstr "" +msgstr "Nätverk som ddns-updater-skripten ska startas på" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:228 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1149 @@ -453,7 +470,7 @@ msgstr "Aldrig" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:494 msgid "New DDns Service…" -msgstr "" +msgstr "Ny DDNS-tjänst…" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1145 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:28 @@ -462,17 +479,17 @@ msgstr "Nästa uppdatering" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1145 msgid "Next Verify" -msgstr "" +msgstr "Nästa verifiering" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:227 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1135 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:40 msgid "No Data" -msgstr "Ingen Data" +msgstr "Inga data" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:383 msgid "No certificates found" -msgstr "Inga ceritifikat hittades" +msgstr "Inga certifikat hittades" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:962 msgid "No logging" @@ -480,12 +497,12 @@ msgstr "Ingen loggning" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:393 msgid "Non-public and by default blocked IPs" -msgstr "" +msgstr "Icke-offentliga och som standard blockerade IP-adresser" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:231 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1121 msgid "Not Running" -msgstr "" +msgstr "Körs inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:964 msgid "Notice" @@ -493,36 +510,41 @@ msgstr "Avisering" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:429 msgid "Number of last lines stored in log files" -msgstr "" +msgstr "Antal senaste rader som lagras i loggfiler" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:916 msgid "OPTIONAL: Force the usage of pure IPv4/IPv6 only communication." -msgstr "" +msgstr "VALFRITT: Tvinga kommunikation enbart via IPv4 eller IPv6." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:937 msgid "OPTIONAL: Force the use of TCP instead of default UDP on DNS requests." msgstr "" +"VALFRITT: Tvinga användning av TCP i stället för standardprotokollet UDP för " +"DNS-begäranden." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:904 msgid "OPTIONAL: Network to use for communication" -msgstr "" +msgstr "VALFRITT: Nätverk som ska användas för kommunikation" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:946 msgid "OPTIONAL: Proxy-Server for detection and updates." -msgstr "" +msgstr "VALFRITT: Proxyserver för identifiering och uppdateringar." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:925 msgid "OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'." msgstr "" +"VALFRITT: Använd en annan DNS-server än standardservern för att identifiera " +"”Registrerad IP”." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1041 msgid "" "On Error the script will stop execution after the given number of retries." msgstr "" +"Vid fel avbryter skriptet körningen efter det angivna antalet omförsök." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:769 msgid "Optional Encoded Parameter" -msgstr "" +msgstr "Valfri kodad parameter" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:775 msgid "Optional Parameter" @@ -530,15 +552,15 @@ msgstr "Valfri parameter" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:770 msgid "Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)" -msgstr "" +msgstr "Valfritt: Ersätter [PARAMENC] i uppdaterings-URL:en (URL-kodad)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:776 msgid "Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)" -msgstr "" +msgstr "Valfritt: Ersätter [PARAMOPT] i uppdaterings-URL:en (INTE URL-kodad)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:945 msgid "PROXY-Server" -msgstr "PROXY-server" +msgstr "Proxyserver" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:760 msgid "Password" @@ -546,34 +568,34 @@ msgstr "Lösenord" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:788 msgid "Path to CA-Certificate" -msgstr "Genväg till CA-certifikat" +msgstr "Sökväg till CA-certifikat" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:312 msgid "" "Please follow the instructions on OpenWrt's homepage to enable IPv6 support" -msgstr "" +msgstr "Följ anvisningarna på OpenWrts webbplats för att aktivera IPv6-stöd" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1086 msgid "Please press [Read] button" -msgstr "Vänligen tryck på [Läs]-knappen" +msgstr "Tryck på knappen [Läs]" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:719 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:741 msgid "Provide either an Update Script OR an Update URL" -msgstr "" +msgstr "Ange antingen ett uppdateringsskript ELLER en uppdaterings-URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1069 msgid "Read / Reread log file" -msgstr "Läs / Läs om loggfilen" +msgstr "Läs/läs om loggfilen" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:693 msgid "Really switch service?" -msgstr "" +msgstr "Vill du verkligen byta tjänst?" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1130 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:30 msgid "Registered IP" -msgstr "Registrerad IP" +msgstr "Registrerad IP-adress" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:567 msgid "Reload" @@ -581,27 +603,27 @@ msgstr "Ladda om" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:550 msgid "Reload this service" -msgstr "" +msgstr "Ladda om den här tjänsten" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:749 msgid "Replaces [DOMAIN] in Update-URL (URL-encoded)" -msgstr "" +msgstr "Ersätter [DOMAIN] i uppdaterings-URL:en (URL-kodad)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:761 msgid "Replaces [PASSWORD] in Update-URL (URL-encoded)" -msgstr "" +msgstr "Ersätter [PASSWORD] i uppdaterings-URL:en (URL-kodad)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:755 msgid "Replaces [USERNAME] in Update-URL (URL-encoded)" -msgstr "" +msgstr "Ersätter [USERNAME] i uppdaterings-URL:en (URL-kodad)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:290 msgid "Restart DDns" -msgstr "" +msgstr "Starta om DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1058 msgid "Retry Unit" -msgstr "" +msgstr "Enhet för omförsök" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:17 msgid "Run once" @@ -620,18 +642,18 @@ msgstr "Skript" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:517 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:657 msgid "Select a service" -msgstr "" +msgstr "Välj en tjänst" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:518 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:659 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:679 msgid "Service doesn't support this IP type" -msgstr "" +msgstr "Tjänsten stöder inte den här IP-typen" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:658 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:669 msgid "Service not installed" -msgstr "" +msgstr "Tjänsten är inte installerad" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:455 msgid "Services" @@ -639,29 +661,31 @@ msgstr "Tjänster" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:450 msgid "Services URL Download" -msgstr "" +msgstr "URL för hämtning av tjänster" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:294 msgid "Services list last update" -msgstr "" +msgstr "Senaste uppdatering av tjänstelistan" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1008 msgid "Setting this parameter to 0 will force the script to only run once" -msgstr "" +msgstr "Om parametern anges till 0 tvingas skriptet att endast köras en gång" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:451 msgid "" "Source URL for services file. Defaults to the master openwrt ddns package " "repo." msgstr "" +"Käll-URL för tjänstefilen. Standardvärdet är huvudarkivet för OpenWrts DDNS-" +"paket." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:201 msgid "Start DDNS" -msgstr "" +msgstr "Starta DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:272 msgid "State" -msgstr "Status" +msgstr "Tillstånd" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1117 msgid "Status" @@ -669,19 +693,19 @@ msgstr "Status" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:410 msgid "Status directory" -msgstr "" +msgstr "Statuskatalog" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:566 msgid "Stop" -msgstr "Stopp" +msgstr "Stoppa" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:201 msgid "Stop DDNS" -msgstr "" +msgstr "Stoppa DDNS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:555 msgid "Stop this service" -msgstr "" +msgstr "Stoppa den här tjänsten" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:19 msgid "Stopped" @@ -689,33 +713,35 @@ msgstr "Stoppad" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:694 msgid "Switch service" -msgstr "" +msgstr "Byt tjänst" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1043 msgid "The default setting of '0' will retry infinitely." -msgstr "" +msgstr "Standardvärdet ”0” innebär obegränsat antal omförsök." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1051 msgid "The interval between which each subsequent retry commences." -msgstr "" +msgstr "Intervallet mellan varje efterföljande omförsök." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:497 msgid "The service name is already used" -msgstr "" +msgstr "Tjänstenamnet används redan" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/status/include/70_ddns.js:43 msgid "There is no service configured." -msgstr "Det finns ingen tjänst inställd." +msgstr "Ingen tjänst är konfigurerad." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:335 msgid "" "This is only a problem with multiple WAN interfaces and your DDNS provider " "is unreachable via one of them." msgstr "" +"Detta är endast ett problem med flera WAN-gränssnitt om din DDNS-leverantör " +"inte kan nås via ett av dem." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1085 msgid "This is the current content of the log file in %h for this service." -msgstr "" +msgstr "Detta är det aktuella innehållet i tjänstens loggfil i %h." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:210 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:277 @@ -723,14 +749,16 @@ msgid "" "This is the default if you run DDNS scripts by yourself (i.e. via cron with " "force_interval set to '0')" msgstr "" +"Detta är standard om du kör DDNS-skript själv (t.ex. via cron med " +"force_interval satt till ”0”)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:893 msgid "This will be autoset to the selected interface" -msgstr "" +msgstr "Detta ställs automatiskt in till det valda gränssnittet" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:608 msgid "Timer Settings" -msgstr "" +msgstr "Timerinställningar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:811 msgid "URL" @@ -738,7 +766,7 @@ msgstr "URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:852 msgid "URL to detect" -msgstr "Webbadress att upptäcka" +msgstr "URL för identifiering" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:229 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:230 @@ -750,19 +778,19 @@ msgstr "Okänd" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:301 msgid "Update DDns Services List" -msgstr "" +msgstr "Uppdatera listan över DDNS-tjänster" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:684 msgid "Update URL" -msgstr "" +msgstr "Uppdaterings-URL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:709 msgid "Update URL for updating your DDNS Provider." -msgstr "" +msgstr "Uppdaterings-URL för att uppdatera din DDNS-leverantör." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:782 msgid "Use HTTP Secure" -msgstr "Använd Säker HTTP" +msgstr "Använd HTTPS" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:435 msgid "Use cURL" @@ -770,7 +798,7 @@ msgstr "Använd cURL" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:871 msgid "User defined script to read system IP-Address" -msgstr "" +msgstr "Användardefinierat skript för att läsa systemets IP-adress" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:754 msgid "Username" @@ -778,19 +806,19 @@ msgstr "Användarnamn" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:371 msgid "Using specific DNS Server not supported" -msgstr "" +msgstr "Användning av en specifik DNS-server stöds inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:990 msgid "Values below 5 minutes == 300 seconds are not supported" -msgstr "" +msgstr "Värden under 5 minuter, dvs. 300 sekunder, stöds inte" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1025 msgid "Values lower than 'Check Interval' except '0' are invalid" -msgstr "" +msgstr "Värden lägre än ”Kontrollintervall”, förutom ”0”, är ogiltiga" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:16 msgid "Verify" -msgstr "Verkställ" +msgstr "Verifiera" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:965 msgid "Warning" @@ -799,51 +827,59 @@ msgstr "Varning" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:504 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:630 msgid "Which record type to update at the DDNS provider (A/AAAA)" -msgstr "" +msgstr "Vilken posttyp som ska uppdateras hos DDNS-leverantören (A/AAAA)" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1059 msgid "Which time units to use for retry counters." -msgstr "" +msgstr "Vilka tidsenheter som ska användas för omförsöksräknare." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:974 msgid "" "Writes detailed messages to log file. File will be truncated automatically." msgstr "" +"Skriver detaljerade meddelanden till loggfilen. Filen trunkeras automatiskt." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:958 msgid "" "Writes log messages to syslog. Critical Errors will always be written to " "syslog." msgstr "" +"Skriver loggmeddelanden till syslog. Kritiska fel skrivs alltid till syslog." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:375 msgid "" "You should install 'bind-host' or 'knot-host' or 'drill' or 'hostip' " "package, if you need to specify a DNS server to detect your registered IP." msgstr "" +"Du bör installera paketet ”bind-host”, ”knot-host”, ”drill” eller ”hostip” " +"om du behöver ange en DNS-server för att identifiera din registrerade IP-" +"adress." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:323 msgid "" "You should install 'wget' or 'curl' or 'uclient-fetch' with 'libustream-" "*ssl' package." msgstr "" +"Du bör installera ”wget” eller ”curl”, eller ”uclient-fetch” tillsammans med " +"paketet ”libustream-*ssl”." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:337 msgid "You should install 'wget' or 'curl' package." -msgstr "" +msgstr "Du bör installera paketet ”wget” eller ”curl”." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:351 msgid "" "You should install 'wget' or 'uclient-fetch' package or replace libcurl." msgstr "" +"Du bör installera paketet ”wget” eller ”uclient-fetch” eller ersätta libcurl." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:349 msgid "cURL is installed, but libcurl was compiled without proxy support." -msgstr "" +msgstr "cURL är installerat, men libcurl kompilerades utan proxystöd." #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:348 msgid "cURL without Proxy Support" -msgstr "cURL utan Proxy-stöd" +msgstr "cURL utan proxystöd" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:513 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:642 @@ -856,7 +892,7 @@ msgstr "dagar" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:789 msgid "directory or path/file" -msgstr "" +msgstr "katalog eller sökväg/fil" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1002 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1036 @@ -875,7 +911,7 @@ msgstr "eller" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:313 msgid "or update your system to the latest OpenWrt Release" -msgstr "" +msgstr "eller uppdatera systemet till den senaste OpenWrt-versionen" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1000 #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1062 @@ -884,4 +920,4 @@ msgstr "sekunder" #: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:793 msgid "to run HTTPS without verification of server certificates (insecure)" -msgstr "för att köra HTTPS utan verifiering av server-certifikaten (osäkert)" +msgstr "för att köra HTTPS utan verifiering av servercertifikat (osäkert)" diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js index 24b2218a..d213eb7c 100644 --- a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/api.js @@ -116,6 +116,22 @@ function processLines(buffer, onChunk) { return buffer; } +// Parse the docker multiplexed stream format, https://docs.docker.com/reference/api/engine/version/v1.43/#stream-format +function parse_multiplexed_stream(buffer) { + const output = []; + + while (buffer.length > 0) { + if (buffer.length < 8) break; // Not enough data for header + let stream_type = buffer.charCodeAt(0); + let payload_length = (buffer.charCodeAt(4) << 24) | (buffer.charCodeAt(5) << 16) | (buffer.charCodeAt(6) << 8) | buffer.charCodeAt(7); + if (buffer.length < 8 + payload_length) break; // Not enough data for payload + let payload = buffer.slice(8, 8 + payload_length); + output.push({ type: stream_type, payload: payload }); + buffer = buffer.slice(8 + payload_length); + } + + return output; +} function call_docker(method, path, options = {}) { return loadPromise.then(() => { @@ -228,20 +244,24 @@ function call_docker(method, path, options = {}) { let parsed = safeText || text; const contentType = response.headers.get('content-type') || ''; - // Try normal JSON parse first - try { - parsed = JSON.parse(text); - } catch (err) { - // If the payload is newline-delimited JSON (Docker events), split and parse each line - if (['application/json', - 'application/x-ndjson', - 'application/json-seq'].includes(contentType) || safeText.includes('\n')) { - const lines = safeText.split(/\r?\n/).filter(Boolean); - try { - parsed = lines.map(l => JSON.parse(l)); - } catch (err2) { - // Fall back to raw text if parsing fails - parsed = text; + if ("application/vnd.docker.multiplexed-stream" == contentType) { + parsed = parse_multiplexed_stream(safeText); + } else { + // Try normal JSON parse first + try { + parsed = JSON.parse(text); + } catch (err) { + // If the payload is newline-delimited JSON (Docker events), split and parse each line + if (['application/json', + 'application/x-ndjson', + 'application/json-seq'].includes(contentType) || safeText.includes('\n')) { + const lines = safeText.split(/\r?\n/).filter(Boolean); + try { + parsed = lines.map(l => JSON.parse(l)); + } catch (err2) { + // Fall back to raw text if parsing fails + parsed = text; + } } } } diff --git a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js index 9e9d89ea..1951a10d 100644 --- a/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js +++ b/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js @@ -1280,13 +1280,14 @@ const ansiToHtml = function(text) { // Escape HTML special characters const escapeHtml = (str) => { const map = { + ' ': ' ', '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; - return str.replace(/[&<>"']/g, m => map[m]); + return str.replace(/[&<>"' ]/g, m => map[m]); }; // Split by ANSI escape sequences and process diff --git a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js index 66f412a6..01cea83b 100644 --- a/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js +++ b/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js @@ -1684,7 +1684,10 @@ return dm2.dv.extend({ return false; } - const logText = response?.body || _('No logs available'); + let logText = response?.body || _('No logs available'); + if ("application/vnd.docker.multiplexed-stream" == response?.headers['content-type']) { + logText = (response?.body || []).map((frame)=>frame.payload).join(''); + } // Convert ANSI codes to HTML and set innerHTML logsDiv.innerHTML = dm2.ansiToHtml(logText); logsDiv.scrollTop = logsDiv.scrollHeight; diff --git a/luci-app-dockerman/po/bn_BD/dockerman.po b/luci-app-dockerman/po/bn_BD/dockerman.po index 8cacc432..28427cee 100644 --- a/luci-app-dockerman/po/bn_BD/dockerman.po +++ b/luci-app-dockerman/po/bn_BD/dockerman.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-10-05 13:02+0000\n" -"Last-Translator: A R Asad \n" +"PO-Revision-Date: 2026-08-06 20:04+0000\n" +"Last-Translator: Mehedi Hasan \n" "Language-Team: Bengali (Bangladesh) \n" "Language: bn_BD\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.14-dev\n" +"X-Generator: Weblate 2026.8.1.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -940,7 +940,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:359 msgid "Import" -msgstr "" +msgstr "ইম্পরট" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:202 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:212 diff --git a/luci-app-dockerman/po/cs/dockerman.po b/luci-app-dockerman/po/cs/dockerman.po index e757576d..4e185805 100644 --- a/luci-app-dockerman/po/cs/dockerman.po +++ b/luci-app-dockerman/po/cs/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-07-13 02:01+0000\n" +"PO-Revision-Date: 2026-08-14 18:35+0000\n" "Last-Translator: Pavel Borecki \n" "Language-Team: Czech \n" @@ -8,7 +8,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n" -"X-Generator: Weblate 2026.7.1.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -866,7 +866,7 @@ msgstr "Název stroje" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:60 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:178 msgid "ID" -msgstr "Identifikátor" +msgstr "Identif." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:45 msgid "IP VLAN" @@ -984,7 +984,7 @@ msgstr "Info" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "Dovnitř" +msgstr "Vstup" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" @@ -1409,7 +1409,7 @@ msgstr "Operační výstup" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:89 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:80 msgid "Options" -msgstr "Možnosti" +msgstr "Předvolby" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:46 msgid "Overlay network" @@ -1641,7 +1641,7 @@ msgstr "Prostředky" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1802 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:242 msgid "Restart" -msgstr "Restartovat" +msgstr "Restart" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:154 msgctxt "daemon restart action" diff --git a/luci-app-dockerman/po/es/dockerman.po b/luci-app-dockerman/po/es/dockerman.po index 47adc224..a192ed4b 100644 --- a/luci-app-dockerman/po/es/dockerman.po +++ b/luci-app-dockerman/po/es/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-22 22:15+0000\n" +"PO-Revision-Date: 2026-08-14 17:53+0000\n" "Last-Translator: Franco Castillo \n" "Language-Team: Spanish \n" @@ -8,7 +8,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2026.7.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -1685,7 +1685,7 @@ msgstr "Ejecutar comando" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:84 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:141 msgid "Running" -msgstr "Ejecutando" +msgstr "Ejecutándose" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:833 msgid "Running Processes" @@ -2093,7 +2093,7 @@ msgstr "Vista general de Volúmenes" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:75 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1763 msgid "Warning" -msgstr "Aviso" +msgstr "Advertencia" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1078 msgid "WebSocket" diff --git a/luci-app-dockerman/po/ga/dockerman.po b/luci-app-dockerman/po/ga/dockerman.po index deae1c84..627d8220 100644 --- a/luci-app-dockerman/po/ga/dockerman.po +++ b/luci-app-dockerman/po/ga/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-07 18:23+0000\n" +"PO-Revision-Date: 2026-08-16 17:38+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin \n" "Language-Team: Irish \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 :" "(n>6 && n<11) ? 3 : 4;\n" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -45,7 +45,7 @@ msgstr "Aisteoir" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Add" -msgstr "Cuir" +msgstr "Cuir leis" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:315 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:620 @@ -254,7 +254,7 @@ msgstr "Glanadh an taisce tógála" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189 msgid "Clear" -msgstr "Soiléir" +msgstr "Glan" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617 msgid "Click to add a new tag to this image" @@ -988,7 +988,7 @@ msgstr "Eolas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "Iontráil" +msgstr "Dul Isteach" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" diff --git a/luci-app-dockerman/po/ja/dockerman.po b/luci-app-dockerman/po/ja/dockerman.po index 5a374dd5..149f444c 100644 --- a/luci-app-dockerman/po/ja/dockerman.po +++ b/luci-app-dockerman/po/ja/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-12-07 21:54+0000\n" +"PO-Revision-Date: 2026-08-05 02:03+0000\n" "Last-Translator: Monarch " "\n" "Language-Team: Japanese \n" "Language-Team: Korean \n" @@ -8,7 +8,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2026.7.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -973,7 +973,7 @@ msgstr "정보(Info)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "" +msgstr "인그레스" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" diff --git a/luci-app-dockerman/po/lo/dockerman.po b/luci-app-dockerman/po/lo/dockerman.po index 1e730576..e11fc660 100644 --- a/luci-app-dockerman/po/lo/dockerman.po +++ b/luci-app-dockerman/po/lo/dockerman.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-05-03 08:19+0000\n" -"Last-Translator: BoneNI \n" +"PO-Revision-Date: 2026-08-15 13:06+0000\n" +"Last-Translator: Bone NI \n" "Language-Team: Lao \n" "Language: lo\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.17.1\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -65,11 +65,11 @@ msgstr "ຂັ້ນສູງ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:590 msgid "Aliases" -msgstr "" +msgstr "ນາມແຝງ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1923 msgid "Aliases (optional):" -msgstr "" +msgstr "ນາມແຝງ (ເລືອກໄດ້):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:342 msgid "All Subtypes" @@ -109,7 +109,7 @@ msgstr "ສາມາດເຊື່ອມຕໍ່ໄດ້" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:139 msgid "Available" -msgstr "ພ້ອມໃຊ້ງານ" +msgstr "ມີຢູ່" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:431 msgid "Back to Containers" @@ -242,7 +242,7 @@ msgstr "ລ້າງ build cache ແລ້ວ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189 msgid "Clear" -msgstr "ລ້າງ" +msgstr "ລ້າງອອກ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617 msgid "Click to add a new tag to this image" @@ -477,7 +477,7 @@ msgstr "ດີບັກ (Debug)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:753 msgid "Default (daemon)" -msgstr "" +msgstr "ຄ່າເລີ່ມຕົ້ນ (daemon)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:57 msgid "Default bridge" @@ -877,7 +877,7 @@ msgstr "ທີ່ຢູ່ IPv4" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1919 msgid "IPv4 Address (optional):" -msgstr "" +msgstr "ທີ່ຢູ່ IPv4 (ເລືອກໄດ້):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:64 msgid "IPv6" @@ -891,7 +891,7 @@ msgstr "ທີ່ຢູ່ IPv6" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1921 msgid "IPv6 Address (optional):" -msgstr "" +msgstr "ທີ່ຢູ່ IPv6 (ເລືອກໄດ້):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:586 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:134 @@ -973,7 +973,7 @@ msgstr "ຂໍ້ມູນ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "Ingress" +msgstr "ຂາເຂົ້າ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" @@ -1085,7 +1085,7 @@ msgstr "ຕົວເລືອກລັອກ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:751 msgid "Log driver" -msgstr "" +msgstr "ໄດເວີການບັນທຶກ (Log driver)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:745 msgid "Log driver options" @@ -1211,7 +1211,7 @@ msgstr "ສະຖິຕິເນັດເວີກ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:282 msgid "Network Aliases" -msgstr "" +msgstr "ນາມແຝງເຄືອຂ່າຍ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:100 msgid "Network Configurations" @@ -1376,7 +1376,7 @@ msgstr "ສຳລັບເນັດເວີກທີ່ຜູ້ໃຊ້ກຳ #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:351 msgid "Open in browser" -msgstr "" +msgstr "ເປີດໃນບຣາວເຊີ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:899 msgid "Operation failed" @@ -1621,7 +1621,7 @@ msgstr "ຊັບພະຍາກອນ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1802 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:242 msgid "Restart" -msgstr "ຣີສະຕາດ" +msgstr "ເລີ່ມໃໝ່" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:154 msgctxt "daemon restart action" @@ -2161,7 +2161,7 @@ msgstr "ອິເມຈ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:755 msgid "json-file" -msgstr "" +msgstr "json-file" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:336 msgid "kill" @@ -2173,7 +2173,7 @@ msgstr "ໂຫຼດ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:754 msgid "local" -msgstr "" +msgstr "local" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:383 msgid "mount" @@ -2189,7 +2189,7 @@ msgstr "node" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:757 msgid "none" -msgstr "" +msgstr "ບໍ່ມີ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:337 msgid "oom" @@ -2258,7 +2258,7 @@ msgstr "stop" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:756 msgid "syslog" -msgstr "" +msgstr "syslog" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:364 msgid "tag" diff --git a/luci-app-dockerman/po/ru/dockerman.po b/luci-app-dockerman/po/ru/dockerman.po index 9e4fe08c..303d1408 100644 --- a/luci-app-dockerman/po/ru/dockerman.po +++ b/luci-app-dockerman/po/ru/dockerman.po @@ -1,7 +1,8 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-07-05 14:58+0000\n" -"Last-Translator: Deni Dgabiev \n" +"PO-Revision-Date: 2026-08-14 12:12+0000\n" +"Last-Translator: SnIPeRSnIPeR " +"\n" "Language-Team: Russian \n" "Language: ru\n" @@ -9,7 +10,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 2026.7.1.dev0\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -253,7 +254,7 @@ msgstr "Кэш сборки очищен" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189 msgid "Clear" -msgstr "Сбросить" +msgstr "Очистить" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617 msgid "Click to add a new tag to this image" @@ -639,7 +640,7 @@ msgstr "Дублировать/Редактировать контейнер: %s #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:422 msgid "Edit" -msgstr "Редактировать" +msgstr "Изменить" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:315 msgid "Edit Mount" @@ -1415,7 +1416,7 @@ msgstr "Результат операции" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:89 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:80 msgid "Options" -msgstr "Опции" +msgstr "Параметры" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:46 msgid "Overlay network" @@ -1472,7 +1473,7 @@ msgstr "Приостановить этот контейнер" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:86 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:143 msgid "Paused" -msgstr "Приостановлено" +msgstr "На паузе" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:742 msgid "Per CPU Stats" @@ -1754,7 +1755,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1801 msgid "Start" -msgstr "Начало" +msgstr "Запустить" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:208 msgid "Start this container" @@ -1773,7 +1774,7 @@ msgstr "Статистика" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:721 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:840 msgid "Status" -msgstr "Статус" +msgstr "Состояние" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1109 msgid "Stderr" @@ -2004,7 +2005,7 @@ msgstr "Возобновить этот контейнер" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Update" -msgstr "Применить" +msgstr "Обновить" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1358 msgid "Updated" diff --git a/luci-app-dockerman/po/sv/dockerman.po b/luci-app-dockerman/po/sv/dockerman.po index 10079314..01f0eb80 100644 --- a/luci-app-dockerman/po/sv/dockerman.po +++ b/luci-app-dockerman/po/sv/dockerman.po @@ -1,22 +1,22 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-03-22 17:11+0000\n" -"Last-Translator: Kristoffer Grundström \n" +"PO-Revision-Date: 2026-08-13 09:16+0000\n" +"Last-Translator: Jonatan Nyberg \n" "Language-Team: Swedish \n" "Language: sv\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.17-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" -msgstr "/mnt/genväg" +msgstr "/mnt/path" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:457 msgid "/path/in/image" -msgstr "/genväg/i/avbild" +msgstr "/path/in/image" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:127 msgid "0" @@ -24,11 +24,11 @@ msgstr "0" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:106 msgid "A Token is preferred over a Password." -msgstr "" +msgstr "En token rekommenderas framför ett lösenord." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:673 msgid "A list of kernel capabilities to add to the container" -msgstr "En lista över kärnfunktioner som du kan lägga till i behållaren" +msgstr "En lista över kärnfunktioner som ska läggas till i containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Action" @@ -36,11 +36,11 @@ msgstr "Åtgärd" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1824 msgid "Action unavailable:" -msgstr "" +msgstr "Åtgärden är inte tillgänglig:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Actor" -msgstr "Skådespelare" +msgstr "Aktör" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Add" @@ -49,15 +49,15 @@ msgstr "Lägg till" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:315 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:620 msgid "Add Mount" -msgstr "" +msgstr "Lägg till montering" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:655 msgid "Add host device to the container" -msgstr "Lägg till värdenhet i container:n" +msgstr "Lägg till värdenhet i containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:681 msgid "Added Capabilities" -msgstr "" +msgstr "Tillagda funktioner" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:634 msgid "Advanced" @@ -65,19 +65,19 @@ msgstr "Avancerat" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:590 msgid "Aliases" -msgstr "" +msgstr "Alias" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1923 msgid "Aliases (optional):" -msgstr "" +msgstr "Alias (valfritt):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:342 msgid "All Subtypes" -msgstr "" +msgstr "Alla undertyper" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:73 msgid "All Types" -msgstr "" +msgstr "Alla typer" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:647 msgid "Allocates an ephemeral host port for all of a container's exposed ports" @@ -91,15 +91,15 @@ msgstr "Alltid" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:234 msgid "Always pull image first" -msgstr "pull alltid image först" +msgstr "Hämta alltid avbilden först" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:146 msgid "" "An overview with the relevant data is displayed here with which the LuCI " "docker client is connected." msgstr "" -"Här visas en översikt med relevanta data som LuCI-dockerklienten är ansluten " -"till." +"Här visas en översikt över relevanta data för Docker-värden som LuCI-" +"klienten är ansluten till." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:33 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:168 @@ -108,7 +108,7 @@ msgstr "API-version" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:70 msgid "Attachable" -msgstr "" +msgstr "Anslutningsbart" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:139 msgid "Available" @@ -116,7 +116,7 @@ msgstr "Tillgänglig" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:431 msgid "Back to Containers" -msgstr "" +msgstr "Tillbaka till containrar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:48 msgid "Base device" @@ -124,20 +124,20 @@ msgstr "Basenhet" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:373 msgid "Bind (host directory)" -msgstr "" +msgstr "Bind-montering (värdkatalog)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:306 msgid "Bind mount a volume" -msgstr "Bind mount en volym" +msgstr "Bind-montera en volym" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:740 msgid "BlkIO Stats" -msgstr "" +msgstr "BlkIO-statistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:675 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:738 msgid "Block IO Weight" -msgstr "Block IO Vikt" +msgstr "Block IO-vikt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:739 msgid "" @@ -155,19 +155,19 @@ msgstr "Bryggenhet" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:419 msgid "Build" -msgstr "" +msgstr "Bygg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:206 msgid "Build an image." -msgstr "" +msgstr "Bygg en avbild." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:72 msgid "" "By entering a valid image name with the corresponding version, the docker " "image can be downloaded from the configured registry." msgstr "" -"Genom att ange ett giltigt image:namn med motsvarande version kan docker-" -"image:n hämtas från det konfigurerade registret." +"Genom att ange ett giltigt avbildsnamn med motsvarande version kan Docker-" +"avbilden hämtas från det konfigurerade registret." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:672 msgid "CAP-ADD(--cap-add)" @@ -179,28 +179,28 @@ msgstr "KOMMANDO" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:690 msgid "CPU Period" -msgstr "" +msgstr "CPU-period" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:625 msgid "CPU Period (microseconds)" -msgstr "" +msgstr "CPU-period (mikrosekunder)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:696 msgid "CPU Quota" -msgstr "" +msgstr "CPU-kvot" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:628 msgid "CPU Quota (microseconds)" -msgstr "" +msgstr "CPU-kvot (mikrosekunder)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:631 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:702 msgid "CPU Shares Weight" -msgstr "CPU-andelar Vikt" +msgstr "Vikt för CPU-andelar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:741 msgid "CPU Stats" -msgstr "" +msgstr "CPU-statistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:766 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:795 @@ -218,7 +218,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:770 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:799 msgid "CPU usage data unavailable" -msgstr "" +msgstr "Data om CPU-användning är inte tillgängliga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:615 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:678 @@ -233,7 +233,7 @@ msgstr "Avbryt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:35 msgid "Causes errors when a chosen API > Docker endpoint API support." -msgstr "" +msgstr "Orsakar fel när valt API är högre än vad Docker-slutpunkten stöder." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:890 msgid "Changes" @@ -241,11 +241,11 @@ msgstr "Ändringar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:420 msgid "Clean" -msgstr "" +msgstr "Rensa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:257 msgid "Cleaned build cache" -msgstr "" +msgstr "Byggcachen har rensats" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189 msgid "Clear" @@ -253,15 +253,15 @@ msgstr "Rensa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617 msgid "Click to add a new tag to this image" -msgstr "" +msgstr "Klicka för att lägga till en ny tagg för den här avbilden" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:531 msgid "Click to remove this tag" -msgstr "" +msgstr "Klicka för att ta bort den här taggen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:213 msgid "Click to view this network" -msgstr "" +msgstr "Klicka för att visa det här nätverket" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:81 msgid "Client connection" @@ -274,17 +274,17 @@ msgstr "Kommando" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1152 msgid "Command cancelled" -msgstr "" +msgstr "Kommandot avbröts" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1131 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1142 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:86 msgid "Command failed" -msgstr "" +msgstr "Kommandot misslyckades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1119 msgid "Command successful" -msgstr "" +msgstr "Kommandot lyckades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1020 msgid "Command:" @@ -292,11 +292,11 @@ msgstr "Kommando:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:80 msgid "Config Only" -msgstr "" +msgstr "Endast konfiguration" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:76 msgid "ConfigFrom" -msgstr "" +msgstr "ConfigFrom" #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:31 msgid "Configuration" @@ -319,7 +319,7 @@ msgstr "Anslut nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1963 msgid "Connect network" -msgstr "" +msgstr "Anslut nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1520 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1532 @@ -328,7 +328,7 @@ msgstr "Ansluten" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1063 msgid "Connected to container console" -msgstr "" +msgstr "Ansluten till containerkonsolen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1405 msgid "Connecting…" @@ -340,30 +340,30 @@ msgstr "Konsol" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1008 msgid "Console Connection" -msgstr "" +msgstr "Konsolanslutning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1522 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1773 msgid "Console connected" -msgstr "" +msgstr "Konsolen är ansluten" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1515 msgid "Console connection closed" -msgstr "" +msgstr "Konsolanslutningen stängdes" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1559 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1794 msgid "Console disconnected" -msgstr "" +msgstr "Konsolen kopplades från" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1579 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1594 msgid "Console is not connected" -msgstr "" +msgstr "Konsolen är inte ansluten" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:160 msgid "Container" -msgstr "" +msgstr "Container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:976 msgid "Container Inspect" @@ -379,11 +379,11 @@ msgstr "Containernamn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:826 msgid "Container Statistics" -msgstr "" +msgstr "Containerstatistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:985 msgid "Container created" -msgstr "" +msgstr "Containern skapades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:446 msgid "Container detail" @@ -391,34 +391,35 @@ msgstr "Detaljer om containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:985 msgid "Container duplicated" -msgstr "" +msgstr "Containern duplicerades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:714 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:721 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:840 msgid "Container is not running" -msgstr "" +msgstr "Containern körs inte" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1094 msgid "Container is not running. Cannot connect to WebSocket console." msgstr "" +"Containern körs inte. Det går inte att ansluta till WebSocket-konsolen." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1015 msgid "Container is not running. Cannot connect to console." -msgstr "" +msgstr "Containern körs inte. Det går inte att ansluta till konsolen." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1385 msgid "Container rename" -msgstr "" +msgstr "Byt namn på container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1393 msgid "Container rename failed" -msgstr "" +msgstr "Det gick inte att byta namn på containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1370 msgid "Container update failed" -msgstr "" +msgstr "Det gick inte att uppdatera containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:577 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:84 @@ -432,7 +433,7 @@ msgstr "Containrar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:100 msgid "Containers overview" -msgstr "Containrar Översikt" +msgstr "Containeröversikt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:421 msgid "Create" @@ -440,23 +441,23 @@ msgstr "Skapa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:979 msgid "Create container" -msgstr "" +msgstr "Skapa container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:1000 msgid "Create container failed" -msgstr "" +msgstr "Det gick inte att skapa containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:234 msgid "Create network" -msgstr "" +msgstr "Skapa nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:249 msgid "Create network failed" -msgstr "" +msgstr "Det gick inte att skapa nätverket" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:63 msgid "Create new docker container" -msgstr "Skapa en ny docker container" +msgstr "Skapa en ny Docker-container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:30 msgid "Create new docker network" @@ -476,15 +477,15 @@ msgstr "DNS" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:588 msgid "DNS Names" -msgstr "" +msgstr "DNS-namn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:73 msgid "Debug" -msgstr "Felsök" +msgstr "Felsökning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:753 msgid "Default (daemon)" -msgstr "" +msgstr "Standard (demon)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:57 msgid "Default bridge" @@ -492,21 +493,23 @@ msgstr "Standardbrygga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:26 msgid "Default ps flags" -msgstr "" +msgstr "Standardflaggor för ps" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:606 msgid "Delete this volume mount" -msgstr "" +msgstr "Ta bort den här volymmonteringen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:104 msgid "" "Destinations prefixed with a Registry host matching an entry in this table " "invoke its corresponding credentials." msgstr "" +"Destinationer med ett registervärdnamn som matchar en post i den här " +"tabellen använder motsvarande autentiseringsuppgifter." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1588 msgid "Detach signal sent (Ctrl+D)" -msgstr "" +msgstr "Frånkopplingssignal skickad (Ctrl+D)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:96 msgid "Detail" @@ -528,11 +531,11 @@ msgstr "Koppla ner" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1862 msgid "Disconnect network" -msgstr "" +msgstr "Koppla från nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1852 msgid "Disconnect network \"%s\" from container?" -msgstr "" +msgstr "Koppla från nätverket ”%s” från containern?" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1125 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1508 @@ -543,7 +546,7 @@ msgstr "Frånkopplad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:536 msgid "Do you want to remove the tag \"%s\"?" -msgstr "" +msgstr "Vill du ta bort taggen ”%s”?" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:16 msgid "Docker - Configuration" @@ -551,7 +554,7 @@ msgstr "Docker - Konfiguration" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:336 msgid "Docker - Container" -msgstr "" +msgstr "Docker – container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:201 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:49 @@ -560,11 +563,11 @@ msgstr "Docker - Containrar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:54 msgid "Docker - Events" -msgstr "" +msgstr "Docker – händelser" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:36 msgid "Docker - Images" -msgstr "Docker - Bilder" +msgstr "Docker – avbilder" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:44 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:57 @@ -573,7 +576,7 @@ msgstr "Docker - Nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:26 msgid "Docker - New Network" -msgstr "" +msgstr "Docker – nytt nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:144 msgid "Docker - Overview" @@ -590,7 +593,7 @@ msgstr "Docker-avbild" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:49 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:171 msgid "Docker Root Dir" -msgstr "Root-mapp för Docker" +msgstr "Docker-rotkatalog" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:167 msgid "Docker Version" @@ -598,11 +601,11 @@ msgstr "Docker-version" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:17 msgid "DockerMan is a simple docker manager client for LuCI" -msgstr "DockerMan är en enkel klient för docker manager för LuCI" +msgstr "DockerMan är en enkel Docker-hanterarklient för LuCI" #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:3 msgid "Dockerman JS" -msgstr "" +msgstr "Dockerman JS" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:953 msgid "Download" @@ -610,18 +613,18 @@ msgstr "Hämta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:276 msgid "Download a valid remote image tar." -msgstr "" +msgstr "Hämta ett giltigt fjärrarkiv med en avbild i tar-format." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:63 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:41 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:187 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:246 msgid "Driver" -msgstr "Drivrutin" +msgstr "drivrutin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:687 msgid "Dropped Capabilities" -msgstr "" +msgstr "Borttagna funktioner" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:407 msgid "Duplicate/Edit" @@ -629,7 +632,7 @@ msgstr "Duplicera/redigera" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:66 msgid "Duplicate/Edit Container: %s" -msgstr "" +msgstr "Duplicera/redigera container: %s" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:422 msgid "Edit" @@ -637,11 +640,11 @@ msgstr "Redigera" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:315 msgid "Edit Mount" -msgstr "" +msgstr "Redigera montering" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:196 msgid "Edit this container" -msgstr "" +msgstr "Redigera den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:122 msgid "Enable IPv6" @@ -649,15 +652,15 @@ msgstr "Aktivera IPv6" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:623 msgid "Enter a new tag for image %s:" -msgstr "" +msgstr "Ange en ny tagg för avbilden %s:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:119 msgid "Enter an optional name and labels for the new volume" -msgstr "" +msgstr "Ange ett valfritt namn och etiketter för den nya volymen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:397 msgid "Enter volume name or pick existing" -msgstr "" +msgstr "Ange ett volymnamn eller välj en befintlig" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:250 msgid "Entry" @@ -700,7 +703,7 @@ msgstr "Fel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1681 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1695 msgid "Error loading logs:" -msgstr "" +msgstr "Fel vid inläsning av loggar:" #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:392 msgid "Events" @@ -712,11 +715,11 @@ msgstr "Exempel:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:84 msgid "Example: tcp://0.0.0.0:2375" -msgstr "Exempel: tcp://0.0.0.0.0:2375" +msgstr "Exempel: tcp://0.0.0.0:2375" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:118 msgid "Exclude IPs" -msgstr "Exkludera IPs" +msgstr "Exkludera IP-adresser" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:417 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:386 @@ -725,7 +728,7 @@ msgstr "Exportera" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:273 msgid "Export this container" -msgstr "" +msgstr "Exportera den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:646 msgid "Exposed All Ports(-P)" @@ -737,36 +740,36 @@ msgstr "Exponerade portar (-p)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1778 msgid "Failed to connect to console" -msgstr "" +msgstr "Det gick inte att ansluta till konsolen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:90 msgid "Failed to execute \"/etc/init.d/%s %s\" action: %s" -msgstr "" +msgstr "Det gick inte att köra åtgärden ”/etc/init.d/%s %s”: %s" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:321 msgid "Failed to load events: %s" -msgstr "" +msgstr "Det gick inte att läsa in händelser: %s" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1683 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1694 msgid "Failed to load logs" -msgstr "" +msgstr "Det gick inte att läsa in loggar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1576 msgid "Failed to send data" -msgstr "" +msgstr "Det gick inte att skicka data" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1591 msgid "Failed to send detach signal" -msgstr "" +msgstr "Det gick inte att skicka frånkopplingssignal" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1730 msgid "Failed to start console" -msgstr "" +msgstr "Det gick inte att starta konsolen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:77 msgid "Fatal" -msgstr "Dödligt" +msgstr "Kritiskt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:924 msgid "File" @@ -774,11 +777,11 @@ msgstr "Fil" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1610 msgid "File uploaded to" -msgstr "" +msgstr "Filen laddades upp till" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:893 msgid "Filesystem Changes" -msgstr "" +msgstr "Ändringar i filsystemet" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:469 msgid "Finish Time" @@ -788,24 +791,26 @@ msgstr "Sluttid" msgid "" "Flags passed to docker top (ps). Leave empty to use the built-in default." msgstr "" +"Flaggor som skickas till docker top (ps). Lämna tomt för att använda den " +"inbyggda standardinställningen." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:50 msgid "For local dockerd socket instances only." -msgstr "" +msgstr "Endast för lokala dockerd-socketinstanser." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:301 msgid "Force Remove" -msgstr "Tvångbortta" +msgstr "Tvinga borttagning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:423 msgid "Force remove" -msgstr "" +msgstr "Tvinga borttagning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1314 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:771 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:153 msgid "Form is not ready yet." -msgstr "" +msgstr "Formuläret är inte klart ännu." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:96 msgid "From" @@ -820,7 +825,7 @@ msgstr "Gateway" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:580 msgid "Global IPv6 Address" -msgstr "" +msgstr "Global IPv6-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:21 msgid "Global settings" @@ -828,7 +833,7 @@ msgstr "Globala inställningar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:23 msgid "Globals" -msgstr "" +msgstr "Globala inställningar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:262 msgid "Go to relevant configuration page" @@ -836,11 +841,11 @@ msgstr "Gå till relevant konfigurationssida" #: applications/luci-app-dockerman/root/usr/share/rpcd/acl.d/luci-app-dockerman.json:3 msgid "Grant UCI access for luci-app-dockerman" -msgstr "Bevilja UCI-åtkomst för luci-app-dockerman" +msgstr "Ge luci-app-dockerman UCI-åtkomst" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:476 msgid "Health Status" -msgstr "" +msgstr "Hälsostatus" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:424 msgid "History" @@ -848,7 +853,7 @@ msgstr "Historik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:448 msgid "Host Directory" -msgstr "" +msgstr "Värdkatalog" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:489 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:640 @@ -873,7 +878,7 @@ msgstr "IP-intervall" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:98 msgid "IPAM Driver" -msgstr "" +msgstr "IPAM-drivrutin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:576 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:267 @@ -883,7 +888,7 @@ msgstr "IPv4-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1919 msgid "IPv4 Address (optional):" -msgstr "" +msgstr "IPv4-adress (valfri):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:64 msgid "IPv6" @@ -893,11 +898,11 @@ msgstr "IPv6" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:272 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:129 msgid "IPv6 Address" -msgstr "" +msgstr "IPv6-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1921 msgid "IPv6 Address (optional):" -msgstr "" +msgstr "IPv6-adress (valfri):" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:586 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:134 @@ -906,7 +911,7 @@ msgstr "IPv6-gateway" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:277 msgid "IPv6 Link-Local Address" -msgstr "" +msgstr "Länklokal IPv6-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:128 msgid "IPv6 Subnet" @@ -914,37 +919,37 @@ msgstr "IPv6-undernät" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:459 msgid "Image" -msgstr "Image" +msgstr "Avbild" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:375 msgid "Image (from image)" -msgstr "" +msgstr "Avbild (från avbild)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:462 msgid "Image ID" -msgstr "" +msgstr "Avbilds-ID" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:113 msgid "Image create completed" -msgstr "" +msgstr "Avbilden har skapats" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:231 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:699 msgid "Image loaded successfully" -msgstr "" +msgstr "Avbilden lästes in" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:188 msgid "Image push completed" -msgstr "" +msgstr "Uppladdning av avbilden slutfördes" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:199 #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:237 msgid "Images" -msgstr "Image" +msgstr "Avbilder" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:337 msgid "Images overview" -msgstr "Image översikt" +msgstr "Avbildsöversikt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:359 msgid "Import" @@ -954,11 +959,11 @@ msgstr "Importera" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:212 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:222 msgid "In Use:" -msgstr "" +msgstr "Används:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1113 msgid "Include logs" -msgstr "" +msgstr "Inkludera loggar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:172 msgid "Index Server Address" @@ -979,13 +984,13 @@ msgstr "Info" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "Inledning" +msgstr "Ingress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" "Ingress network is the network which provides the routing-mesh in swarm mode" msgstr "" -"Ingressnätverket är det nätverk som tillhandahåller routningsnätet i swarm " +"Ingress-nätverket är det nätverk som tillhandahåller routningsmeshen i swarm-" "läge" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:425 @@ -1013,33 +1018,32 @@ msgstr "Ipvlan-läge" msgid "" "It replaces the daemon registry mirrors with a new set of registry mirrors" msgstr "" -"Den ersätter daemonens registry speglar med en ny uppsättning registry " -"speglar" +"Det ersätter demonens registerspeglar med en ny uppsättning registerspeglar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:382 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1804 msgid "Kill" -msgstr "Döda" +msgstr "Tvångsavsluta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:261 msgid "Kill this container" -msgstr "" +msgstr "Tvångsavsluta den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:896 msgid "Kind" -msgstr "" +msgstr "Typ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:68 msgid "L2 bridge" -msgstr "L2-bryggan" +msgstr "L2-brygga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:69 msgid "L3 bridge" -msgstr "L3-bryggan" +msgstr "L3-brygga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:94 msgid "LAN connection" -msgstr "" +msgstr "LAN-anslutning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:92 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:84 @@ -1051,7 +1055,7 @@ msgstr "Etiketter" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1171 msgid "Lines to show:" -msgstr "" +msgstr "Rader att visa:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:514 msgid "Links" @@ -1063,27 +1067,29 @@ msgstr "Länkar till andra containrar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:309 msgid "Load Events" -msgstr "" +msgstr "Läs in händelser" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1184 msgid "Load Logs" -msgstr "" +msgstr "Läs in loggar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1676 msgid "Loading logs…" -msgstr "" +msgstr "Läser in loggar…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:34 msgid "Lock API endpoint to a specific version (helps guarantee behaviour)." msgstr "" +"Lås API-slutpunkten till en viss version (hjälper till att garantera " +"beteendet)." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1163 msgid "Log Controls" -msgstr "" +msgstr "Loggkontroller" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:693 msgid "Log Driver" -msgstr "" +msgstr "Loggdrivrutin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:71 msgid "Log Level" @@ -1091,11 +1097,11 @@ msgstr "Loggnivå" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:696 msgid "Log Options" -msgstr "" +msgstr "Loggalternativ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:751 msgid "Log driver" -msgstr "" +msgstr "Loggdrivrutin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:745 msgid "Log driver options" @@ -1107,7 +1113,7 @@ msgstr "Loggar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:582 msgid "MAC Address" -msgstr "" +msgstr "MAC-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:44 msgid "MAC VLAN" @@ -1115,15 +1121,15 @@ msgstr "MAC VLAN" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:122 msgid "Mac Address" -msgstr "" +msgstr "MAC-adress" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:341 msgid "Manage and view container configuration" -msgstr "" +msgstr "Hantera och visa containerkonfiguration" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:739 msgid "Mem Stats" -msgstr "" +msgstr "Minnesstatistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:709 msgid "Memory" @@ -1131,16 +1137,16 @@ msgstr "Minne" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:651 msgid "Memory + Swap" -msgstr "" +msgstr "Minne + växlingsutrymme" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:635 msgid "Memory Limit" -msgstr "" +msgstr "Minnesgräns" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:662 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:725 msgid "Memory Reservation" -msgstr "" +msgstr "Minnesreservation" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:758 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:789 @@ -1158,19 +1164,19 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:762 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:793 msgid "Memory usage data unavailable" -msgstr "" +msgstr "Data om minnesanvändning är inte tillgängliga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:803 msgid "Metric" -msgstr "" +msgstr "Mått" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:697 msgid "Microseconds of CPU time that the container can get in a CPU period" -msgstr "" +msgstr "Mikrosekunder CPU-tid som containern kan få under en CPU-period" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1657 msgid "Missing header or CORS interfering" -msgstr "" +msgstr "Rubrik saknas eller CORS stör kommunikationen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:430 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:55 @@ -1179,7 +1185,7 @@ msgstr "Läge" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:501 msgid "Mount Path" -msgstr "" +msgstr "Monteringssökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:254 msgid "Mount Point" @@ -1187,7 +1193,7 @@ msgstr "Monteringspunkt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:661 msgid "Mount tmpfs directory" -msgstr "Montera tmpfs-katalogen" +msgstr "Montera tmpfs-katalog" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:305 msgid "Mount(--mount)" @@ -1195,7 +1201,7 @@ msgstr "Montera(--mount)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:549 msgid "Mounts/Binds" -msgstr "Monteringar/Bindningar" +msgstr "Monteringar/bindningar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:455 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:567 @@ -1209,23 +1215,23 @@ msgstr "Namn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:213 msgid "Name of the container that can be selected during container creation" -msgstr "" +msgstr "Namn på containern som kan väljas när en container skapas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:38 msgid "Name of the network that can be selected during container creation" -msgstr "Namn på det nätverk som kan väljas när behållaren skapas" +msgstr "Namnet på nätverket som kan väljas när containern skapas" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:738 msgid "Net Stats" -msgstr "" +msgstr "Nätverksstatistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:282 msgid "Network Aliases" -msgstr "" +msgstr "Nätverksalias" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:100 msgid "Network Configurations" -msgstr "" +msgstr "Nätverkskonfigurationer" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:59 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:37 @@ -1234,23 +1240,23 @@ msgstr "Nätverksnamn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1967 msgid "Network connected" -msgstr "" +msgstr "Nätverket anslöts" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:242 msgid "Network created" -msgstr "" +msgstr "Nätverket skapades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:240 msgid "Network created with warning" -msgstr "" +msgstr "Nätverket skapades med varning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1866 msgid "Network disconnected" -msgstr "" +msgstr "Nätverket kopplades från" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1143 msgid "Network error" -msgstr "" +msgstr "Nätverksfel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:557 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:254 @@ -1263,15 +1269,15 @@ msgstr "Nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:49 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:62 msgid "Networks overview" -msgstr "Nätverk Översikt" +msgstr "Nätverksöversikt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:987 msgid "New container duplicated from" -msgstr "" +msgstr "Ny container duplicerad från" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:988 msgid "New container has been created." -msgstr "" +msgstr "En ny container har skapats." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:622 msgid "New tag" @@ -1279,7 +1285,7 @@ msgstr "Ny tagg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:118 msgid "New volume" -msgstr "" +msgstr "Ny volym" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:484 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:679 @@ -1289,65 +1295,67 @@ msgstr "Nej" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1877 msgid "No additional networks available to connect" -msgstr "" +msgstr "Inga ytterligare nätverk finns att ansluta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:877 msgid "No changes needed" -msgstr "" +msgstr "Inga ändringar behövs" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:348 msgid "No copy (nocopy)" -msgstr "" +msgstr "Ingen kopiering (nocopy)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:251 msgid "No events found" -msgstr "" +msgstr "Inga händelser hittades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:898 msgid "No filesystem changes detected" -msgstr "" +msgstr "Inga ändringar i filsystemet identifierades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:161 msgid "No image tags available" -msgstr "" +msgstr "Inga avbildstaggar är tillgängliga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:386 msgid "No images selected" -msgstr "" +msgstr "Inga avbilder har valts" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1687 msgid "No logs available" -msgstr "" +msgstr "Inga loggar är tillgängliga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:972 msgid "No name specified." -msgstr "" +msgstr "Inget namn har angetts." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1945 msgid "No network selected" -msgstr "" +msgstr "Inget nätverk har valts" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:855 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1281 msgid "No processes running" -msgstr "" +msgstr "Inga processer körs" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:805 msgid "No statistics available" -msgstr "" +msgstr "Ingen statistik är tillgänglig" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:614 msgid "No volumes available" -msgstr "" +msgstr "Inga volymer är tillgängliga" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:83 msgid "" "Note that dockerd no longer listens on IP:port without TLS options after v27." msgstr "" +"Observera att dockerd från och med v27 inte längre lyssnar på IP:port utan " +"TLS-alternativ." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:51 msgid "Note: docker provides no container import facility." -msgstr "" +msgstr "Observera: Docker har ingen funktion för import av containrar." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:116 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:151 @@ -1367,36 +1375,36 @@ msgstr "OK" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:673 msgid "OOM Kill Disable" -msgstr "" +msgstr "Inaktivera OOM-avslutning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:487 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:252 msgid "On failure" -msgstr "" +msgstr "Vid fel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:37 msgid "" "On this page all images are displayed that are available on the system and " "with which a container can be created." msgstr "" -"På den här sidan visas alla images som är tillgängliga på systemet och med " -"vilka en images kan skapas." +"På den här sidan visas alla avbilder som finns tillgängliga i systemet och " +"som kan användas för att skapa en container." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:263 msgid "Only for user-defined networks" -msgstr "" +msgstr "Endast för användardefinierade nätverk" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:351 msgid "Open in browser" -msgstr "" +msgstr "Öppna i webbläsare" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:899 msgid "Operation failed" -msgstr "" +msgstr "Åtgärden misslyckades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:542 msgid "Operational Output" -msgstr "" +msgstr "Åtgärdsutdata" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:466 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:495 @@ -1419,7 +1427,7 @@ msgstr "PID" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:737 msgid "PID Stats" -msgstr "" +msgstr "PID-statistik" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:189 msgid "Parent Interface" @@ -1435,18 +1443,18 @@ msgstr "Lösenord" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:896 msgid "Path" -msgstr "Genväg" +msgstr "sökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1640 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1663 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1664 msgid "Path error" -msgstr "" +msgstr "Sökvägsfel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:936 msgid "Path:" -msgstr "Genväg:" +msgstr "Sökväg:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:396 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1805 @@ -1455,50 +1463,50 @@ msgstr "Pausa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:207 msgid "Pause this container" -msgstr "" +msgstr "Pausa den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:86 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:143 msgid "Paused" -msgstr "Pausades" +msgstr "Pausad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:742 msgid "Per CPU Stats" -msgstr "" +msgstr "Statistik per CPU" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:550 msgid "Please choose a directory or enter a volume name" -msgstr "" +msgstr "Välj en katalog eller ange ett volymnamn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:555 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:563 msgid "Please enter a mount path" -msgstr "" +msgstr "Ange en monteringssökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:559 msgid "Please enter a subpath" -msgstr "" +msgstr "Ange en undersökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:293 msgid "Please enter an image repo tag" -msgstr "" +msgstr "Ange en tagg för avbildsarkivet" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:299 msgid "Please enter an image source" -msgstr "" +msgstr "Ange en avbildskälla" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:102 msgid "Please enter an image tag" -msgstr "" +msgstr "Ange en avbildstagg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:176 msgid "Please select an image tag to push" -msgstr "" +msgstr "Välj en avbildstagg att ladda upp" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1029 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1621 msgid "Please specify a path" -msgstr "" +msgstr "Ange en sökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1035 msgid "Port:" @@ -1507,11 +1515,11 @@ msgstr "Port:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:507 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:164 msgid "Ports" -msgstr "Portar" +msgstr "portar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:60 msgid "Private (Prevent communication between MAC VLANs)" -msgstr "Privat (förhindra kommunikation mellan MAC VLANs)" +msgstr "Privat (förhindra kommunikation mellan MAC-VLAN)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:240 msgid "Privileged" @@ -1519,7 +1527,7 @@ msgstr "Privilegierad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:678 msgid "Privileged Mode" -msgstr "" +msgstr "Privilegierat läge" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:830 msgid "Processes" @@ -1527,63 +1535,65 @@ msgstr "Processer" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:337 msgid "Propagation: private" -msgstr "" +msgstr "Spridning: private" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:338 msgid "Propagation: rprivate" -msgstr "" +msgstr "Spridning: rprivate" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:340 msgid "Propagation: rshared" -msgstr "" +msgstr "Spridning: rshared" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:342 msgid "Propagation: rslave" -msgstr "" +msgstr "Spridning: rslave" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:339 msgid "Propagation: shared" -msgstr "" +msgstr "Spridning: shared" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:341 msgid "Propagation: slave" -msgstr "" +msgstr "Spridning: slave" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:429 msgid "Prune" -msgstr "" +msgstr "Rensa oanvända resurser" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:626 msgid "Publish container's port(s) to the host" -msgstr "Publicera behållarens port(ar) till värden" +msgstr "Publicera containerns port(ar) på värden" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:361 msgid "Pull" -msgstr "Pull" +msgstr "Hämta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:362 msgid "Push" -msgstr "" +msgstr "Ladda upp" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:143 msgid "" "Push an image to a registry. Select an image tag from all available tags on " "the system." msgstr "" +"Ladda upp en avbild till ett register. Välj en avbildstagg bland alla " +"tillgängliga taggar i systemet." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:817 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:881 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:913 msgid "Raw JSON" -msgstr "" +msgstr "Rå JSON" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:335 msgid "Read-only (ro)" -msgstr "" +msgstr "Skrivskyddad (ro)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:336 msgid "Read-write (rw)" -msgstr "" +msgstr "Läs- och skrivbar (rw)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:125 msgid "Registry" @@ -1591,7 +1601,7 @@ msgstr "Register" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:100 msgid "Registry Auth" -msgstr "" +msgstr "Registerautentisering" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:63 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:173 @@ -1606,7 +1616,7 @@ msgstr "Ta bort" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1814 msgid "Remove container?" -msgstr "" +msgstr "Ta bort containern?" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:535 msgid "Remove tag" @@ -1614,16 +1624,16 @@ msgstr "Ta bort tagg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:625 msgid "Repository" -msgstr "" +msgstr "Arkiv" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:658 msgid "Repository cannot be empty" -msgstr "" +msgstr "Arkivet får inte vara tomt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:755 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:787 msgid "Resource Usage" -msgstr "" +msgstr "Resursanvändning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:607 msgid "Resources" @@ -1647,7 +1657,7 @@ msgstr "Policy för omstart" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:238 msgid "Restart this container" -msgstr "" +msgstr "Starta om den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:88 msgid "Restrict external access to the network" @@ -1664,7 +1674,7 @@ msgstr "Körs" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:833 msgid "Running Processes" -msgstr "" +msgstr "Processer som körs" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:192 msgid "Running:" @@ -1672,38 +1682,38 @@ msgstr "Körs:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "STAT" -msgstr "" +msgstr "STAT" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:427 msgid "Save locally" -msgstr "" +msgstr "Spara lokalt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:62 msgid "Scope" -msgstr "" +msgstr "Omfattning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:91 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:336 msgid "Select Type First" -msgstr "" +msgstr "Välj typ först" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:159 msgid "Select image tag" -msgstr "" +msgstr "Välj avbildstagg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1917 msgid "Select network to connect:" -msgstr "" +msgstr "Välj nätverk att ansluta:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:361 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:474 msgid "Select options..." -msgstr "" +msgstr "Välj alternativ..." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1150 msgid "Send" -msgstr "" +msgstr "Skicka" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:291 msgid "Set custom DNS servers" @@ -1711,7 +1721,7 @@ msgstr "Ange anpassade DNS-servrar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:301 msgid "Set environment variables inside the container" -msgstr "" +msgstr "Ange miljövariabler i containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:72 msgid "Set the logging level" @@ -1722,6 +1732,8 @@ msgid "" "Set your LAN interface when docker listens on all addresses like 0.0.0.0 " "or ::." msgstr "" +"Ange ditt LAN-gränssnitt när Docker lyssnar på alla adresser, exempelvis " +"0.0.0.0 eller ::." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:423 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:599 @@ -1733,6 +1745,7 @@ msgid "" "Specifies where the Docker daemon will listen for client connections. " "default:" msgstr "" +"Anger var Docker-demonen ska lyssna efter klientanslutningar. Standard:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1801 @@ -1741,11 +1754,11 @@ msgstr "Starta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:208 msgid "Start this container" -msgstr "" +msgstr "Starta den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:161 msgid "State" -msgstr "" +msgstr "Tillstånd" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:703 msgid "Stats" @@ -1760,38 +1773,38 @@ msgstr "Status" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1109 msgid "Stderr" -msgstr "" +msgstr "Stderr" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1101 msgid "Stdin" -msgstr "" +msgstr "Stdin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1105 msgid "Stdout" -msgstr "" +msgstr "Stdout" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:372 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1803 msgid "Stop" -msgstr "Stopp" +msgstr "Stoppa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:155 msgctxt "daemon stop action" msgid "Stop" -msgstr "Stopp" +msgstr "Stoppa" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:249 msgid "Stop this container" -msgstr "" +msgstr "Stoppa den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:88 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:145 msgid "Stopped" -msgstr "" +msgstr "Stoppad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1098 msgid "Streams:" -msgstr "" +msgstr "Strömmar:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:104 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:103 @@ -1801,24 +1814,24 @@ msgstr "Undernät" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:460 msgid "Subpath" -msgstr "" +msgstr "Undersökväg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:81 msgid "Subtype" -msgstr "" +msgstr "Undertyp" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1522 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1773 msgid "Success" -msgstr "" +msgstr "Lyckades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:1012 msgid "Successful" -msgstr "" +msgstr "Lyckades" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:542 msgid "Sysctl Settings" -msgstr "" +msgstr "Sysctl-inställningar" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:666 msgid "Sysctl(--sysctl)" @@ -1834,31 +1847,31 @@ msgstr "TTY (-t)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1763 msgid "TTYd may still be starting up" -msgstr "" +msgstr "TTYd kan fortfarande hålla på att starta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:635 msgid "Tag" -msgstr "" +msgstr "Tagg" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:670 msgid "Tag added successfully" -msgstr "" +msgstr "Taggen lades till" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:554 msgid "Tag removed successfully" -msgstr "" +msgstr "Taggen togs bort" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:105 msgid "The first match is used." -msgstr "" +msgstr "Den första matchningen används." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:641 msgid "The hostname to use for the container" -msgstr "Värdnamnet som ska användas för behållaren" +msgstr "Värdnamnet som ska användas för containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:691 msgid "The length of a CPU period in microseconds" -msgstr "" +msgstr "Längden på en CPU-period i mikrosekunder" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:746 msgid "The logging configuration for this container" @@ -1869,12 +1882,16 @@ msgid "" "The user that commands are run as inside the container. (format: name|" "uid[:group|gid])" msgstr "" +"Användaren som kommandon körs som i containern. (format: namn|uid[:grupp|" +"gid])" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:50 msgid "" "This page displays all docker Containers that have been created on the " "connected docker host." msgstr "" +"Den här sidan visar alla Docker-containrar som har skapats på den anslutna " +"Docker-värden." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:45 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/networks.js:58 @@ -1882,14 +1899,16 @@ msgid "" "This page displays all docker networks that have been created on the " "connected docker host." msgstr "" -"På den här sidan visas alla docker-nätverk som har skapats på den anslutna " -"docker-värden." +"Den här sidan visar alla Docker-nätverk som har skapats på den anslutna " +"Docker-värden." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:57 msgid "" "This page displays all docker volumes that have been created on the " "connected docker host." msgstr "" +"Den här sidan visar alla Docker-volymer som har skapats på den anslutna " +"Docker-värden." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249 msgid "Time" @@ -1901,7 +1920,7 @@ msgstr "Tmpfs" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:528 msgid "Tmpfs Directories" -msgstr "" +msgstr "Tmpfs-kataloger" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:660 msgid "Tmpfs(--tmpfs)" @@ -1913,11 +1932,11 @@ msgstr "Till" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:138 msgid "Token" -msgstr "" +msgstr "Token" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:107 msgid "Tokes and Passwords are not encrypted in the uci configuration." -msgstr "" +msgstr "Token och lösenord krypteras inte i UCI-konfigurationen." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:82 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:139 @@ -1939,7 +1958,7 @@ msgstr "Totalt minne" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:210 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:220 msgid "Total:" -msgstr "" +msgstr "Totalt:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:498 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:63 @@ -1949,15 +1968,15 @@ msgstr "Typ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1135 msgid "Type command here... (Ctrl+D to detach)" -msgstr "" +msgstr "Skriv kommandot här... (Ctrl+D för att koppla från)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "USER" -msgstr "" +msgstr "ANVÄNDARE" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:980 msgid "Unexpected error" -msgstr "" +msgstr "Oväntat fel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1354 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639 @@ -1968,16 +1987,16 @@ msgstr "Okänt fel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:485 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:250 msgid "Unless stopped" -msgstr "" +msgstr "Om inte stoppad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:396 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1806 msgid "Unpause" -msgstr "" +msgstr "Återuppta" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:208 msgid "Unpause this container" -msgstr "" +msgstr "Återuppta den här containern" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Update" @@ -1985,7 +2004,7 @@ msgstr "Uppdatera" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1358 msgid "Updated" -msgstr "" +msgstr "Uppdaterad" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:428 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:948 @@ -1994,20 +2013,20 @@ msgstr "Ladda upp" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1609 msgid "Uploading file to container…" -msgstr "" +msgstr "Laddar upp fil till containern…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:698 msgid "Uploading image…" -msgstr "" +msgstr "Laddar upp avbild…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1608 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:697 msgid "Uploading…" -msgstr "" +msgstr "Laddar upp…" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:103 msgid "Used for push/pull operations on custom registries." -msgstr "" +msgstr "Används för upp- och hämtning mot anpassade register." #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:115 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:479 @@ -2025,25 +2044,25 @@ msgstr "VEPA (Virtual Ethernet Port Aggregator)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "VSZ" -msgstr "" +msgstr "VSZ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:803 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:244 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:251 msgid "Value" -msgstr "" +msgstr "Värde" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:240 msgid "Version" -msgstr "" +msgstr "Version" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:374 msgid "Volume (named)" -msgstr "" +msgstr "Volym (namngiven)" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:401 msgid "Volume Name" -msgstr "" +msgstr "Volymnamn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:219 #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:348 @@ -2052,7 +2071,7 @@ msgstr "Volymer" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:61 msgid "Volumes overview" -msgstr "Volymer översikt" +msgstr "Volymöversikt" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:75 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1763 @@ -2061,15 +2080,15 @@ msgstr "Varning" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1078 msgid "WebSocket" -msgstr "" +msgstr "WebSocket" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1087 msgid "WebSocket Console" -msgstr "" +msgstr "WebSocket-konsol" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1499 msgid "WebSocket error" -msgstr "" +msgstr "WebSocket-fel" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:679 msgid "Yes" @@ -2077,35 +2096,35 @@ msgstr "Ja" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:323 msgid "attach" -msgstr "" +msgstr "attach" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:404 msgid "builder" -msgstr "" +msgstr "builder" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:324 msgid "commit" -msgstr "" +msgstr "commit" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1835 msgid "completed" -msgstr "" +msgstr "slutförd" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:405 msgid "config" -msgstr "" +msgstr "config" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:372 msgid "connect" -msgstr "" +msgstr "connect" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:406 msgid "container" -msgstr "" +msgstr "container" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:325 msgid "copy" -msgstr "" +msgstr "copy" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:326 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:357 @@ -2113,109 +2132,109 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:382 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:391 msgid "create" -msgstr "" +msgstr "create" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:407 msgid "daemon" -msgstr "" +msgstr "daemon" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:358 msgid "delete" -msgstr "" +msgstr "delete" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:327 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:374 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:385 msgid "destroy" -msgstr "" +msgstr "destroy" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:328 msgid "detach" -msgstr "" +msgstr "detach" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:329 msgid "die" -msgstr "" +msgstr "die" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:373 msgid "disconnect" -msgstr "" +msgstr "disconnect" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:330 msgid "exec_create" -msgstr "" +msgstr "exec_create" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:331 msgid "exec_detach" -msgstr "" +msgstr "exec_detach" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:333 msgid "exec_die" -msgstr "" +msgstr "exec_die" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:332 msgid "exec_start" -msgstr "" +msgstr "exec_start" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:334 msgid "export" -msgstr "" +msgstr "export" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:206 msgid "git repositories require git installed on the docker host." -msgstr "" +msgstr "Git-arkiv kräver att git är installerat på Docker-värden." #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:335 msgid "health_status" -msgstr "" +msgstr "health_status" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:408 msgid "image" -msgstr "" +msgstr "image" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:755 msgid "json-file" -msgstr "" +msgstr "json-file" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:336 msgid "kill" -msgstr "" +msgstr "kill" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:360 msgid "load" -msgstr "" +msgstr "load" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:754 msgid "local" -msgstr "" +msgstr "lokal" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:383 msgid "mount" -msgstr "" +msgstr "mount" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:409 msgid "network" -msgstr "" +msgstr "network" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:410 msgid "node" -msgstr "" +msgstr "node" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:757 msgid "none" -msgstr "" +msgstr "ingen" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:337 msgid "oom" -msgstr "" +msgstr "oom" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:338 msgid "pause" -msgstr "" +msgstr "pause" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:411 msgid "plugin" -msgstr "" +msgstr "plugin" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:318 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:347 @@ -2223,96 +2242,96 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:377 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:386 msgid "prune" -msgstr "" +msgstr "prune" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:869 msgid "ps flags:" -msgstr "" +msgstr "ps-flaggor:" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:352 msgid "reload" -msgstr "" +msgstr "reload" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:376 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:393 msgid "remove" -msgstr "" +msgstr "remove" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:339 msgid "rename" -msgstr "" +msgstr "rename" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:340 msgid "resize" -msgstr "" +msgstr "resize" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:341 msgid "restart" -msgstr "" +msgstr "restart" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:363 msgid "save" -msgstr "" +msgstr "save" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:412 msgid "secret" -msgstr "" +msgstr "secret" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:413 msgid "service" -msgstr "" +msgstr "service" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:342 msgid "start" -msgstr "" +msgstr "start" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:343 msgid "stop" -msgstr "" +msgstr "stop" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:756 msgid "syslog" -msgstr "" +msgstr "syslog" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:364 msgid "tag" -msgstr "" +msgstr "tag" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:437 msgid "tmpfs Options" -msgstr "" +msgstr "tmpfs-alternativ" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:344 msgid "top" -msgstr "" +msgstr "top" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:384 msgid "unmount" -msgstr "" +msgstr "unmount" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:345 msgid "unpause" -msgstr "" +msgstr "unpause" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:365 msgid "untag" -msgstr "" +msgstr "untag" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:346 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:375 #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:392 msgid "update" -msgstr "" +msgstr "update" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:414 msgid "volume" -msgstr "" +msgstr "volym" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:126 msgid "volume name" -msgstr "" +msgstr "volymnamn" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1360 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:991 msgid "with warnings" -msgstr "" +msgstr "med varningar" diff --git a/luci-app-dockerman/po/tr/dockerman.po b/luci-app-dockerman/po/tr/dockerman.po index 6ea0780a..4c48013e 100644 --- a/luci-app-dockerman/po/tr/dockerman.po +++ b/luci-app-dockerman/po/tr/dockerman.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-09 01:01+0000\n" +"PO-Revision-Date: 2026-07-28 20:29+0000\n" "Last-Translator: Oğuz Ersen \n" "Language-Team: Turkish \n" @@ -8,7 +8,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.8.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -251,7 +251,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189 msgid "Clear" -msgstr "" +msgstr "Temizle" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617 msgid "Click to add a new tag to this image" @@ -326,7 +326,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1520 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1532 msgid "Connected" -msgstr "" +msgstr "Bağlandı" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1063 msgid "Connected to container console" @@ -541,7 +541,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1538 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1557 msgid "Disconnected" -msgstr "" +msgstr "Bağlantı kesildi" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:536 msgid "Do you want to remove the tag \"%s\"?" @@ -1285,7 +1285,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:679 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:249 msgid "No" -msgstr "" +msgstr "Hayır" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1877 msgid "No additional networks available to connect" @@ -2073,7 +2073,7 @@ msgstr "" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:679 msgid "Yes" -msgstr "" +msgstr "Evet" #: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:323 msgid "attach" diff --git a/luci-app-dockerman/po/zh_Hans/dockerman.po b/luci-app-dockerman/po/zh_Hans/dockerman.po index 02430c6c..2b2711fc 100644 --- a/luci-app-dockerman/po/zh_Hans/dockerman.po +++ b/luci-app-dockerman/po/zh_Hans/dockerman.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-06-05 19:02+0000\n" -"Last-Translator: 大王叫我来巡山 " +"PO-Revision-Date: 2026-08-15 13:57+0000\n" +"Last-Translator: Hosted Weblate user 54392 " "\n" "Language-Team: Chinese (Simplified Han script) \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2026.6\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505 msgid "/mnt/path" @@ -45,7 +45,7 @@ msgstr "操作方" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:582 msgid "Add" -msgstr "新增" +msgstr "添加" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:315 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:620 @@ -972,7 +972,7 @@ msgstr "信息" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network.js:73 #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:72 msgid "Ingress" -msgstr "入口" +msgstr "入站" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/network_new.js:73 msgid "" @@ -1398,7 +1398,7 @@ msgstr "覆盖网络" #: applications/luci-app-dockerman/root/usr/share/luci/menu.d/luci-app-dockerman.json:19 msgid "Overview" -msgstr "概况" +msgstr "概览" #: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:847 msgid "PID" diff --git a/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc b/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc index 5fb85394..da6bed52 100755 --- a/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc +++ b/luci-app-dockerman/root/usr/share/rpcd/ucode/docker_rpc.uc @@ -100,6 +100,23 @@ function coerce_values_to_string(obj) { return obj; }; +// Parse the docker multiplexed stream format, https://docs.docker.com/reference/api/engine/version/v1.43/#stream-format +function parse_multiplexed_stream(buffer) { + let output = []; + + while (length(buffer) > 0) { + if (length(buffer) < 8) break; // Not enough data for header + let stream_type = ord(buffer, 0); + let payload_length = ord(buffer, 4) << 24 | ord(buffer, 5) << 16 | ord(buffer, 6) << 8 | ord(buffer, 7); + if (length(buffer) < 8 + payload_length) break; // Not enough data for payload + let payload = substr(buffer, 8, payload_length); + push(output, { type: stream_type, payload: payload }); + buffer = substr(buffer, 8 + payload_length); + } + + return output; +}; + function call_docker(method, path, options) { options = options || {}; const headers = options.headers || {}; @@ -277,6 +294,8 @@ function call_docker(method, path, options) { } else { response_body = data; } + } else if (response_headers['content-type'] === 'application/vnd.docker.multiplexed-stream' && response_body) { + response_body = parse_multiplexed_stream(response_body); } return { diff --git a/luci-app-ksmbd/po/lo/ksmbd.po b/luci-app-ksmbd/po/lo/ksmbd.po index 858a0dcd..d52f217f 100644 --- a/luci-app-ksmbd/po/lo/ksmbd.po +++ b/luci-app-ksmbd/po/lo/ksmbd.po @@ -1,14 +1,14 @@ msgid "" msgstr "" -"PO-Revision-Date: 2026-05-03 08:19+0000\n" -"Last-Translator: BoneNI \n" +"PO-Revision-Date: 2026-08-15 13:06+0000\n" +"Last-Translator: Bone NI \n" "Language-Team: Lao \n" "Language: lo\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.17.1\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" @@ -84,7 +84,7 @@ msgstr "ສືບທອດເຈົ້າຂອງ" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:36 msgid "Interface" -msgstr "ອິນເຕີເຟດ" +msgstr "ອິນເຕີເຟສ" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:37 msgid "Listen only on the given interface or, if unspecified, on lan" diff --git a/luci-app-ksmbd/po/sv/ksmbd.po b/luci-app-ksmbd/po/sv/ksmbd.po index 1cfa4643..ff364643 100644 --- a/luci-app-ksmbd/po/sv/ksmbd.po +++ b/luci-app-ksmbd/po/sv/ksmbd.po @@ -1,22 +1,23 @@ msgid "" msgstr "" -"PO-Revision-Date: 2025-07-14 19:03+0000\n" -"Last-Translator: Kristoffer Grundström \n" +"PO-Revision-Date: 2026-08-13 09:16+0000\n" +"Last-Translator: Jonatan Nyberg \n" "Language-Team: Swedish \n" "Language: sv\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.13-dev\n" +"X-Generator: Weblate 2026.9.dev0\n" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:58 msgid "Add optional guest access to IPC$ share, disabled by default" msgstr "" +"Lägg till valfri gäståtkomst till IPC$-resursen, inaktiverad som standard" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:57 msgid "Allow guest on IPC$." -msgstr "" +msgstr "Tillåt gäståtkomst till IPC$." #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:103 msgid "Allow guests" @@ -24,15 +25,15 @@ msgstr "Tillåt gäster" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:60 msgid "Allow legacy (insecure) protocols/authentication." -msgstr "TIllåt förlegade (osäkra) protokoll/autentisering." +msgstr "Tillåt äldre (osäkra) protokoll/autentiseringsmetoder." #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:61 msgid "" "Allow legacy smb(v1)/Lanman connections, needed for older devices without " "smb(v2.1/3) support." msgstr "" -"Tillåt förlegade smb(v1)/Lanman-anslutningar, behövs för äldre enheter som " -"saknar stöd för smb(v2.1/3)." +"Tillåt äldre SMBv1-/Lanman-anslutningar. Krävs för äldre enheter utan stöd " +"för SMBv2.1/3." #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:100 msgid "Allowed users" @@ -44,7 +45,7 @@ msgstr "Bläddringsbar" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:119 msgid "Create mask" -msgstr "Skapa mask" +msgstr "Skapandemask" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:54 msgid "Description" @@ -52,7 +53,7 @@ msgstr "Beskrivning" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:125 msgid "Directory mask" -msgstr "Mask för mapp" +msgstr "Katalogmask" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:34 msgid "Edit Template" @@ -65,19 +66,19 @@ msgstr "" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:98 msgid "Force Root" -msgstr "" +msgstr "Tvinga root" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:33 msgid "General Settings" -msgstr "Generella inställningar" +msgstr "Allmänna inställningar" #: applications/luci-app-ksmbd/root/usr/share/rpcd/acl.d/luci-app-ksmbd.json:3 msgid "Grant access to LuCI app ksmbd" -msgstr "Tillåt åtkomst till LuCI-app ksmbd" +msgstr "Ge LuCI-appen ksmbd åtkomst" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:114 msgid "Hide dot files" -msgstr "" +msgstr "Dölj punktfiler" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:109 msgid "Inherit owner" @@ -89,7 +90,8 @@ msgstr "Gränssnitt" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:37 msgid "Listen only on the given interface or, if unspecified, on lan" -msgstr "Lyssna endast på det givna gränssnittet eller, om ospecifierat, på LAN" +msgstr "" +"Lyssna endast på det angivna gränssnittet eller, om inget anges, på LAN" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:81 msgid "Name" @@ -98,25 +100,27 @@ msgstr "Namn" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:22 #: applications/luci-app-ksmbd/root/usr/share/luci/menu.d/luci-app-ksmbd.json:3 msgid "Network Shares" -msgstr "Nätverksdelningar" +msgstr "Nätverksutdelningar" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:82 msgid "Path" -msgstr "Genväg" +msgstr "sökväg" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:77 msgid "" "Please add directories to share. Each directory refers to a folder on a " "mounted device." msgstr "" +"Lägg till kataloger som ska delas. Varje katalog avser en mapp på en " +"monterad enhet." #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:92 msgid "Read-only" -msgstr "Endast läsbar" +msgstr "Skrivskyddad" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:76 msgid "Shared Directories" -msgstr "Delade mappar" +msgstr "Delade kataloger" #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:65 msgid "" @@ -125,6 +129,9 @@ msgid "" "('|') should not be changed. They get their values from the 'General " "Settings' tab." msgstr "" +"Detta är innehållet i filen '/etc/ksmbd/ksmbd.conf.template', som din ksmbd-" +"konfiguration genereras från. Värden mellan lodstreck ('|') ska inte ändras. " +"De hämtas från fliken 'Allmänna inställningar'." #: applications/luci-app-ksmbd/htdocs/luci-static/resources/view/ksmbd.js:51 msgid "Workgroup" diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 4d818855..55ecece1 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:=239 +PKG_RELEASE:=240 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua index debfae77..4ba553c5 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua @@ -25,25 +25,25 @@ o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") o.default = o.keylist[3] if true then - m:appendTemplate("/node_list/node_list") + m:appendTemplate("/node_list/node_list") - if luci.http.formvalue("cbi.submit") == "1" then - local group_order = {} - group_order = luci.http.formvaluetable("group.order") - if group_order then - for k, v in pairs(group_order) do - if v and v~= "" then - local new_order = {} - string.gsub(v, "[^" .. " " .. "]+", function(w) - new_order[#new_order + 1] = w - end) - for idx, name in ipairs(new_order) do - m.uci:reorder(m.config, name, idx - 1) - end - end - end - end - end + if luci.http.formvalue("cbi.submit") == "1" then + local group_order = {} + group_order = luci.http.formvaluetable("group.order") + if group_order then + for k, v in pairs(group_order) do + if v and v~= "" then + local new_order = {} + string.gsub(v, "[^" .. " " .. "]+", function(w) + new_order[#new_order + 1] = w + end) + for idx, name in ipairs(new_order) do + m.uci:reorder(m.config, name, idx - 1) + end + end + end + end + end end return api.return_map(m) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua index d6827d49..2904eab2 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua @@ -117,9 +117,9 @@ end o = s:option(DummyValue, "_update", translate("Manual subscription All")) o.rawhtml = true o.cfgvalue = function(self, section) - return string.format([[ - ]], - translate("Manual subscription All")) + return string.format([[ + ]], + translate("Manual subscription All")) end s = m:section(TypedSection, "subscribe_list", "", "" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "") @@ -215,9 +215,9 @@ end o = s:option(DummyValue, "_update", translate("Manual subscription")) o.rawhtml = true o.cfgvalue = function(self, section) - return string.format([[ - ]], - section, translate("Manual subscription")) + return string.format([[ + ]], + section, translate("Manual subscription")) end m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype}) diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index c2037d48..cf8fa84b 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -916,12 +916,12 @@ function gen_config(var) if xray_settings.fragment == "1" then local lengths, delays = {}, {} api.trim(xray_settings.fragment_lengths):gsub("[^,]+", function(w) - w = w:gsub("%s+", "") - if w ~= "" then lengths[#lengths+1] = w end + w = w:gsub("%s+", "") + if w ~= "" then lengths[#lengths+1] = w end end) api.trim(xray_settings.fragment_delays):gsub("[^,]+", function(w) - w = w:gsub("%s+", "") - if w ~= "" then delays[#delays+1] = w end + w = w:gsub("%s+", "") + if w ~= "" then delays[#delays+1] = w end end) fragment_table = { type = "fragment", diff --git a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm index d470ea4b..3aa8f39f 100644 --- a/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm +++ b/luci-app-passwall/luasrc/view/passwall/cbi/nodes_value_com.htm @@ -534,7 +534,7 @@ //自定义框 function v_customEnter(cbid, labelSpan, hiddenInput, searchInput, panel, listContainer, customInput) { let inputValue = customInput.value.trim(); - if (!inputValue) { + if (!inputValue) { return; } const existingItems = listContainer.querySelectorAll('li[data-key="' + inputValue + '"]'); diff --git a/luci-app-passwall/luasrc/view/passwall/global/faq.htm b/luci-app-passwall/luasrc/view/passwall/global/faq.htm index db4d1e31..6dad6239 100644 --- a/luci-app-passwall/luasrc/view/passwall/global/faq.htm +++ b/luci-app-passwall/luasrc/view/passwall/global/faq.htm @@ -3,42 +3,42 @@ local map = self.map local api = map.api -%>
    - <%:DNS related issues:%> + <%:DNS related issues:%>
  • 1. <%:Certain browsers such as Chrome have built-in DNS service, which may affect DNS resolution settings. You can go to 'Settings -> Privacy and security -> Use secure DNS' menu to turn it off.%>
  • 2. <%:If you are unable to access the internet after reboot, please try clearing the cache of your terminal devices (make sure to close all open browser application windows first, this step is especially important):%> -
    • <%:For Windows systems, open Command Prompt and run the command 'ipconfig /flushdns'.%>
    • -
    • <%:For Mac systems, open Terminal and run the command 'sudo killall -HUP mDNSResponder'.%>
    • -
    • <%:For mobile devices, you can clear it by reconnecting to the network, such as toggling Airplane Mode and reconnecting to WiFi.%>
    • -
    +
    • <%:For Windows systems, open Command Prompt and run the command 'ipconfig /flushdns'.%>
    • +
    • <%:For Mac systems, open Terminal and run the command 'sudo killall -HUP mDNSResponder'.%>
    • +
    • <%:For mobile devices, you can clear it by reconnecting to the network, such as toggling Airplane Mode and reconnecting to WiFi.%>
    • +
  • 3. <%:Please make sure your device's network settings point both the DNS server and default gateway to this router, to ensure DNS queries are properly routed.%>
@@ -58,7 +58,7 @@ local api = map.api } var dom = document.getElementById("faq_reset"); - if (dom) { + if (dom) { var li = ""; li += "" + "<%: Hide in main menu:%>"+ "" + "
" + "<%: Browser access: %>" + "" + hide_url + "" + "
"; li += "" + "<%: Show in main menu:%>"+ "" + "
" +"<%: Browser access: %>" + "" + show_url + "" + "
"; diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm index 899540c9..3a425c5a 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm @@ -43,7 +43,7 @@ table td, .table .td { z-index: 99; text-align: center; background: white; - box-shadow: darkgrey 10px 10px 30px 5px; + box-shadow: darkgrey 10px 10px 30px 5px; text-align: center; padding: 0.5em; } @@ -55,8 +55,8 @@ table td, .table .td { .ping a, .ping_value a, .tcping_value a { - text-decoration: none; - cursor: pointer; + text-decoration: none; + cursor: pointer; } @media (prefers-color-scheme: dark) { diff --git a/luci-app-passwall/luasrc/view/passwall/rule/shunt_rule_list.htm b/luci-app-passwall/luasrc/view/passwall/rule/shunt_rule_list.htm index 2fbbd8c8..c41e24eb 100644 --- a/luci-app-passwall/luasrc/view/passwall/rule/shunt_rule_list.htm +++ b/luci-app-passwall/luasrc/view/passwall/rule/shunt_rule_list.htm @@ -15,8 +15,8 @@ table th, .table .th { table td, .table .td { text-align: center; - /* white-space: nowrap; */ - word-break: keep-all; + /* white-space: nowrap; */ + word-break: keep-all; } .td.cbi-section-actions { diff --git a/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm b/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm index 7ea144ad..f15a1860 100644 --- a/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm +++ b/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm @@ -75,7 +75,7 @@ local api = map.api if (!value) { alert("<%:Please enter query content!%>"); return; - } + } lookup_btn.disabled = true; extract_btn.disabled = true; btn.value = '<%:Querying%>'; diff --git a/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall b/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall index 56aad8ca..b99f5ed1 100644 --- a/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall +++ b/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall @@ -9,7 +9,7 @@ EEOF } if [ -e "/etc/config/ucitrack" ]; then - uci -q batch <<-EOF + uci -q batch <<-EOF delete ucitrack.@passwall[-1] add ucitrack passwall set ucitrack.@passwall[-1].init=passwall diff --git a/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall_server b/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall_server index 08a11f75..c9f362c4 100644 --- a/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall_server +++ b/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall_server @@ -9,7 +9,7 @@ EEOF } if [ -e "/etc/config/ucitrack" ]; then - uci -q batch <<-EOF + uci -q batch <<-EOF delete ucitrack.@passwall_server[-1] add ucitrack passwall_server set ucitrack.@passwall_server[-1].init=passwall_server diff --git a/luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.lua b/luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.lua index 00272008..f85274db 100644 --- a/luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.lua +++ b/luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.lua @@ -1,814 +1,814 @@ -local api = require "luci.passwall.api" -local sys = api.sys -local fs = api.fs -local datatypes = api.datatypes -local TMP = {} - -local function tinsert(table_name, val) - if table_name and type(table_name) == "table" then - if not TMP[table_name] then - TMP[table_name] = {} - end - if TMP[table_name][val] then - return false - end - table.insert(table_name, val) - TMP[table_name][val] = true - return true - end - return false -end - -local function backup_servers() - local DNSMASQ_DNS = api.uci_get("dhcp", "@dnsmasq[0]", "server") - if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then - api.uci_set_c("@global[0]", "dnsmasq_servers", DNSMASQ_DNS) - api.uci_save_c(true) - end -end - -local function restore_servers() - local dns_table = {} - local DNSMASQ_DNS = api.uci_get("dhcp", "@dnsmasq[0]", "server") - if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then - for k, v in ipairs(DNSMASQ_DNS) do - tinsert(dns_table, v) - end - end - local OLD_SERVER = api.uci_get(api.c_config, "@global[0]", "dnsmasq_servers") - if OLD_SERVER and #OLD_SERVER > 0 then - for k, v in ipairs(OLD_SERVER) do - tinsert(dns_table, v) - end - api.uci_del_c("@global[0]", "dnsmasq_servers") - api.uci_save_c(true) - end - if dns_table and #dns_table > 0 then - api.uci_set("dhcp", "@dnsmasq[0]", "server", dns_table) - api.uci_save(nil, "dhcp", true) - end -end - -function stretch() - local dnsmasq_server = api.uci_get("dhcp", "@dnsmasq[0]", "server") - local dnsmasq_noresolv = api.uci_get("dhcp", "@dnsmasq[0]", "noresolv") - local _flag - if dnsmasq_server and #dnsmasq_server > 0 then - for k, v in ipairs(dnsmasq_server) do - if not v:find("/") then - _flag = true - end - end - end - if not _flag and dnsmasq_noresolv == "1" then - api.uci_del("dhcp", "@dnsmasq[0]", "noresolv") - local RESOLVFILE = "/tmp/resolv.conf.d/resolv.conf.auto" - local file = io.open(RESOLVFILE, "r") - if not file then - RESOLVFILE = "/tmp/resolv.conf.auto" - else - local size = file:seek("end") - file:close() - if size == 0 then - RESOLVFILE = "/tmp/resolv.conf.auto" - end - end - api.uci_set("dhcp", "@dnsmasq[0]", "resolvfile", RESOLVFILE) - api.uci_save(nil, "dhcp", true) - end -end - -function restart(var) - local LOG = var["-LOG"] - sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") - if LOG == "1" then - api.log("重启 dnsmasq 服务") - end -end - -function logic_restart(var) - local LOG = var["-LOG"] - local DEFAULT_DNS = api.get_cache_var("DEFAULT_DNS") - if DEFAULT_DNS then - backup_servers() - --sys.call("sed -i '/list server/d' /etc/config/dhcp >/dev/null 2>&1") - local dns_table = {} - local dnsmasq_server = api.uci_get("dhcp", "@dnsmasq[0]", "server") - if dnsmasq_server and #dnsmasq_server > 0 then - for k, v in ipairs(dnsmasq_server) do - if v:find("/") then - tinsert(dns_table, v) - end - end - api.uci_set("dhcp", "@dnsmasq[0]", "server", dns_table) - api.uci_save(nil, "dhcp", true) - end - sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") - restore_servers() - else - sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") - end - if LOG == "1" then - api.log("重启 dnsmasq 服务") - end -end - -function copy_instance(var) - local LISTEN_PORT = var["-LISTEN_PORT"] - local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] - local conf_lines = {} - local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')") - for line in io.lines("/tmp/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do - local filter - if line:find("passwall") then filter = true end - if line:find("ubus") then filter = true end - if line:find("dhcp") then filter = true end - if line:find("server=") == 1 then filter = true end - if line:find("port=") == 1 then filter = true end - if line:find("conf%-dir=") == 1 then - filter = true - if TMP_DNSMASQ_PATH then - local tmp_path = line:sub(1 + #"conf-dir=") - sys.call(string.format("cp -r %s/* %s/ 2>/dev/null", tmp_path, TMP_DNSMASQ_PATH)) - end - end - if line:find("address=") == 1 or (line:find("server=") == 1 and line:find("/")) then filter = nil end - if not filter then - tinsert(conf_lines, line) - end - end - tinsert(conf_lines, "port=" .. LISTEN_PORT) - if TMP_DNSMASQ_PATH then - sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall*") - end - if var["-return"] == "1" then - return conf_lines - end - if #conf_lines > 0 then - local DNSMASQ_CONF = var["-DNSMASQ_CONF"] - local conf_out = io.open(DNSMASQ_CONF, "a") - conf_out:write(table.concat(conf_lines, "\n")) - conf_out:write("\n") - conf_out:close() - end -end - -function add_rule(var) - local FLAG = var["-FLAG"] - local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] - local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"] - local LISTEN_PORT = var["-LISTEN_PORT"] - local DEFAULT_DNS = var["-DEFAULT_DNS"] - local LOCAL_DNS = var["-LOCAL_DNS"] - local TUN_DNS = var["-TUN_DNS"] - local USE_DEFAULT_DNS = var["-USE_DEFAULT_DNS"] - local CHINADNS_DNS = var["-CHINADNS_DNS"] - local NODE = var["-NODE"] - local USE_DIRECT_LIST = var["-USE_DIRECT_LIST"] - local USE_PROXY_LIST = var["-USE_PROXY_LIST"] - local USE_BLOCK_LIST = var["-USE_BLOCK_LIST"] - local USE_GFW_LIST = var["-USE_GFW_LIST"] - local CHN_LIST = var["-CHN_LIST"] - local DEFAULT_PROXY_MODE = var["-DEFAULT_PROXY_MODE"] - local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"] - local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"] - local NFTFLAG = var["-NFTFLAG"] - local CACHE_PATH = api.CACHE_PATH - local CACHE_FLAG = "dnsmasq_" .. FLAG - local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG - local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt" - local USE_CHINADNS_NG = "0" - local IS_SHUNT_NODE = api.uci_get_c(NODE, "protocol") == "_shunt" - local USE_GEOVIEW = api.uci_get_c("@global_rules[0]", "enable_geoview") - - local list1 = {} - local excluded_domain = {} - - if not api.is_finded("geoview") then - USE_GEOVIEW = "0" - end - - local function log(...) - if NO_LOGIC_LOG == "1" then - return - end - api.log(...) - end - - local function check_dns(domain, dns) - if domain == "" or domain:find("#") then - return false - end - if not dns then - return - end - for k,v in ipairs(list1[domain].dns) do - if dns == v then - return true - end - end - return false - end - - local function check_ipset(domain, ipset) - if domain == "" or domain:find("#") then - return false - end - if not ipset then - return - end - for k,v in ipairs(list1[domain].ipsets) do - if ipset == v then - return true - end - end - return false - end - - local function set_domain_address(domain, address) - if domain == "" or domain:find("#") then - return - end - if not list1[domain] then - list1[domain] = { - dns = {}, - ipsets = {} - } - end - if not list1[domain].address then - list1[domain].address = address - end - end - - local function set_domain_dns(domain, dns) - if domain == "" or domain:find("#") then - return - end - if not dns then - return - end - if not list1[domain] then - list1[domain] = { - dns = {}, - ipsets = {} - } - end - for line in string.gmatch(dns, '[^' .. "," .. ']+') do - if not check_dns(domain, line) then - table.insert(list1[domain].dns, line) - end - end - end - - local function set_domain_ipset(domain, ipset) - if domain == "" or domain:find("#") then - return - end - if not ipset then - return - end - if not list1[domain] then - list1[domain] = { - dns = {}, - ipsets = {} - } - end - for line in string.gmatch(ipset, '[^' .. "," .. ']+') do - if not check_ipset(domain, line) then - table.insert(list1[domain].ipsets, line) - end - end - end - - local function add_excluded_domain(domain) - if domain == "" or domain:find("#") then - return - end - excluded_domain[domain] = true - end - - local function check_excluded_domain(domain) - if domain == "" or domain:find("#") then - return false - end - if excluded_domain[domain] then - return true - end - local pos = domain:find(".", 1, true) - while pos do - if excluded_domain[domain:sub(pos + 1)] then - return true - end - pos = domain:find(".", pos + 1, true) - end - return false - end - - local function foreach_geosite(list_arg, callback) - local geosite_path = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/" - geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat" - if not fs.access(geosite_path) then return end - local bin = api.finded_com("geoview") - if not (bin and list_arg) then return end - local cmd = string.format("%q -type geosite -action extract -input %q -list %q -lowmem=true", bin, geosite_path, list_arg) - local pipe = io.popen(cmd) - if not pipe then return end - for line in pipe:lines() do - if line ~= "" then callback(line) end - end - pipe:close() - end - - local cache_text = "" - local nodes_address_md5 = sys.exec("echo -n $(uci show passwall | grep '\\.address') | md5sum") - local new_rules = sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)") - local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. USE_DEFAULT_DNS .. CHINADNS_DNS .. USE_DIRECT_LIST .. USE_PROXY_LIST .. USE_BLOCK_LIST .. USE_GFW_LIST .. CHN_LIST .. DEFAULT_PROXY_MODE .. NO_PROXY_IPV6 .. nodes_address_md5 .. new_rules .. NFTFLAG - if fs.access(CACHE_TEXT_FILE) then - for line in io.lines(CACHE_TEXT_FILE) do - cache_text = line - end - end - - if cache_text ~= new_text then - api.remove(CACHE_DNS_PATH .. "*") - end - - local dnsmasq_default_dns - if USE_DEFAULT_DNS ~= "nil" then - if USE_DEFAULT_DNS == "direct" then - dnsmasq_default_dns = LOCAL_DNS - end - if USE_DEFAULT_DNS == "remote" then - dnsmasq_default_dns = TUN_DNS - end - if USE_DEFAULT_DNS == "remote" and CHN_LIST == "direct" then - dnsmasq_default_dns = TUN_DNS - end - end - - local only_global - if DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0" then - --没有启用中国列表和GFW列表时 - dnsmasq_default_dns = TUN_DNS - only_global = 1 - end - if USE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then - dnsmasq_default_dns = CHINADNS_DNS - USE_CHINADNS_NG = "1" - end - - local setflag_4= (NFTFLAG == "1") and "4#inet#passwall#" or "" - local setflag_6= (NFTFLAG == "1") and "6#inet#passwall#" or "" - - if not fs.access(CACHE_DNS_PATH) then - fs.mkdir(CACHE_DNS_PATH) - - --屏蔽列表 - if USE_CHINADNS_NG == "0" and USE_BLOCK_LIST == "1" then - local geosite_arg = "" - local f = io.open("/usr/share/passwall/rules/block_host") - if f then - for line in f:lines() do - if not line:find("#") and line:find("geosite:") then - line = string.match(line, ":([^:]+)$") - geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line - else - line = api.get_std_domain(line) - if line ~= "" and not line:find("#") and not line:find(":") then - set_domain_address(line, "") - end - end - end - f:close() - end - if USE_GEOVIEW == "1" and geosite_arg ~= "" then - foreach_geosite(geosite_arg, function(line) - set_domain_address(line, "") - end) - log(" - 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成") - end - end - - local fwd_dns - local no_ipv6 - - --始终用国内DNS解析节点域名 - if true then - fwd_dns = LOCAL_DNS - if USE_CHINADNS_NG == "1" then - fwd_dns = nil - else - local sets = { - setflag_4 .. "psw_vps", - setflag_6 .. "psw_vps6" - } - local function process_address(address) - address = (address or ""):lower() - if api.vps_domain_exclude(address) then return end - if datatypes.hostname(address) then - set_domain_dns(address, fwd_dns) - set_domain_ipset(address, table.concat(sets, ",")) - end - end - api.uci_foreach_c("nodes", function(t) - process_address(t.address) - process_address(t.download_address) - local dns, _ = api.get_domain_port_from_url(t.domain_resolver_dns or t.domain_resolver_dns_https or "") - if dns and dns ~= "" then - process_address(dns) - end - end) - api.uci_foreach_c("subscribe_list", function(t) --订阅链接 - local url, _ = api.get_domain_port_from_url(t.url or "") - if url and url ~= "" then - process_address(url) - end - end) - log(string.format(" - 节点列表中的域名(vpslist):%s", fwd_dns or "默认")) - end - end - - --直连(白名单)列表 - if USE_DIRECT_LIST == "1" then - fwd_dns = LOCAL_DNS - if USE_CHINADNS_NG == "1" then - fwd_dns = nil - end - if fwd_dns then - local sets = { - setflag_4 .. "psw_white", - setflag_6 .. "psw_white6" - } - --始终用国内DNS解析直连(白名单)列表 - local geosite_arg = "" - local f = io.open("/usr/share/passwall/rules/direct_host") - if f then - for line in f:lines() do - if not line:find("#") and line:find("geosite:") then - line = string.match(line, ":([^:]+)$") - geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line - else - line = api.get_std_domain(line) - if line ~= "" and not line:find("#") and not line:find(":") then - add_excluded_domain(line) - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end - end - end - f:close() - log(string.format(" - 域名白名单(whitelist):%s", fwd_dns or "默认")) - end - if USE_GEOVIEW == "1" and geosite_arg ~= "" then - foreach_geosite(geosite_arg, function(line) - add_excluded_domain(line) - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end) - log(" - 解析[直连列表] Geosite 到域名白名单(whitelist)完成") - end - end - end - - --代理(黑名单)列表 - if USE_PROXY_LIST == "1" then - fwd_dns = TUN_DNS - if USE_CHINADNS_NG == "1" then - fwd_dns = nil - end - if fwd_dns then - local set_name = "psw_black" - local set6_name = "psw_black6" - if FLAG ~= "default" then - set_name = "psw_" .. FLAG .. "_black" - set6_name = "psw_" .. FLAG .. "_black6" - end - local sets = { - setflag_4 .. set_name - } - if NO_PROXY_IPV6 ~= "1" then - table.insert(sets, setflag_6 .. set6_name) - end - --始终使用远程DNS解析代理(黑名单)列表 - local geosite_arg = "" - local f = io.open("/usr/share/passwall/rules/proxy_host") - if f then - for line in f:lines() do - if not line:find("#") and line:find("geosite:") then - line = string.match(line, ":([^:]+)$") - geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line - else - line = api.get_std_domain(line) - if line ~= "" and not line:find("#") and not line:find(":") then - add_excluded_domain(line) - if NO_PROXY_IPV6 == "1" then - set_domain_address(line, "::") - end - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end - end - end - f:close() - log(string.format(" - 代理域名表(blacklist):%s", fwd_dns or "默认")) - end - if USE_GEOVIEW == "1" and geosite_arg ~= "" then - foreach_geosite(geosite_arg, function(line) - add_excluded_domain(line) - if NO_PROXY_IPV6 == "1" then - set_domain_address(line, "::") - end - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end) - log(" - 解析[代理列表] Geosite 到代理域名表(blacklist)完成") - end - end - end - - --GFW列表 - if USE_GFW_LIST == "1" then - fwd_dns = TUN_DNS - if USE_CHINADNS_NG == "1" then - fwd_dns = nil - end - if fwd_dns then - local set_name = "psw_gfw" - local set6_name = "psw_gfw6" - if FLAG ~= "default" then - set_name = "psw_" .. FLAG .. "_gfw" - set6_name = "psw_" .. FLAG .. "_gfw6" - end - local sets = { - setflag_4 .. set_name - } - if NO_PROXY_IPV6 ~= "1" then - table.insert(sets, setflag_6 .. set6_name) - end - local f = io.open("/usr/share/passwall/rules/gfwlist") - if f then - for line in f:lines() do - if line ~= "" and not line:find("#") and not check_excluded_domain(line) then - if NO_PROXY_IPV6 == "1" then - set_domain_address(line, "::") - end - if dnsmasq_default_dns == fwd_dns then - fwd_dns = nil - else - set_domain_dns(line, fwd_dns) - end - set_domain_ipset(line, table.concat(sets, ",")) - end - end - f:close() - log(string.format(" - 防火墙域名表(gfwlist):%s", fwd_dns or "默认")) - end - end - end - - --中国列表 - if CHN_LIST ~= "0" then - fwd_dns = nil - if CHN_LIST == "direct" then - fwd_dns = LOCAL_DNS - end - if CHN_LIST == "proxy" then - fwd_dns = TUN_DNS - end - if USE_CHINADNS_NG == "1" then - fwd_dns = nil - end - if fwd_dns then - local sets = { - setflag_4 .. "psw_chn", - setflag_6 .. "psw_chn6" - } - if CHN_LIST == "proxy" then - if NO_PROXY_IPV6 == "1" then - sets = { - setflag_4 .. "psw_chn" - } - end - end - local f = io.open("/usr/share/passwall/rules/chnlist") - if f then - for line in f:lines() do - if line ~= "" and not line:find("#") and not check_excluded_domain(line) then - if CHN_LIST == "proxy" and NO_PROXY_IPV6 == "1" then - set_domain_address(line, "::") - end - if dnsmasq_default_dns == fwd_dns then - fwd_dns = nil - else - set_domain_dns(line, fwd_dns) - end - set_domain_ipset(line, table.concat(sets, ",")) - end - end - f:close() - log(string.format(" - 中国域名表(chnroute):%s", fwd_dns or "默认")) - end - end - end - - --分流规则 - if IS_SHUNT_NODE and USE_CHINADNS_NG == "0" then - local t = api.uci_get_c(NODE) - local default_node_id = t["default_node"] or "_direct" - api.uci_foreach_c("shunt_rules", function(s) - local _node_id = t[s[".name"]] - if _node_id and _node_id ~= "_blackhole" and t["shunt_group"] == s.group then - if _node_id == "_default" then - _node_id = default_node_id - end - - fwd_dns = nil - no_ipv6 = nil - - local sets = {} - - if _node_id == "_direct" then - fwd_dns = LOCAL_DNS - if USE_DIRECT_LIST == "1" then - table.insert(sets, setflag_4 .. "psw_white") - table.insert(sets, setflag_6 .. "psw_white6") - else - local set_name = "psw_shunt" - local set6_name = "psw_shunt6" - if FLAG ~= "default" then - set_name = "psw_" .. FLAG .. "_shunt" - set6_name = "psw_" .. FLAG .. "_shunt6" - end - table.insert(sets, setflag_4 .. set_name) - table.insert(sets, setflag_6 .. set6_name) - end - else - local set_name = "psw_shunt" - local set6_name = "psw_shunt6" - if FLAG ~= "default" then - set_name = "psw_" .. FLAG .. "_shunt" - set6_name = "psw_" .. FLAG .. "_shunt6" - end - fwd_dns = TUN_DNS - table.insert(sets, setflag_4 .. set_name) - if NO_PROXY_IPV6 ~= "1" then - table.insert(sets, setflag_6 .. set6_name) - else - no_ipv6 = true - end - end - - local domain_list = s.domain_list or "" - local geosite_arg = "" - for line in string.gmatch(domain_list, "[^\r\n]+") do - if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then - if line:find("geosite:") then - line = string.match(line, ":([^:]+)$") - geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line - else - if line:find("domain:") or line:find("full:") then - line = string.match(line, ":([^:]+)$") - end - line = api.get_std_domain(line) - add_excluded_domain(line) - - if no_ipv6 then - set_domain_address(line, "::") - end - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end - end - end - - if USE_GFW_LIST == "1" and CHN_LIST == "0" and USE_GEOVIEW == "1" and geosite_arg ~= "" then --仅GFW模式解析geosite - foreach_geosite(geosite_arg, function(line) - add_excluded_domain(line) - if no_ipv6 then - set_domain_address(line, "::") - end - set_domain_dns(line, fwd_dns) - set_domain_ipset(line, table.concat(sets, ",")) - end) - log(string.format(" - 解析分流规则(%s) Geosite 完成", s.remarks)) - end - - if _node_id ~= "_direct" then - log(string.format(" - Sing-Box/Xray分流规则(%s):%s", s.remarks, fwd_dns or "默认")) - end - end - end) - elseif only_global == 1 and NO_PROXY_IPV6 == "1" then - --节点:固定节点 - --代理模式:全局模式 - --过滤代理域名 IPv6:启用 - --禁止解析所有IPv6记录 - list1["#"] = { - dns = {}, - ipsets = {}, - address = "::" - } - end - - if list1 and next(list1) then - local address_out = io.open(CACHE_DNS_PATH .. "/000-address.conf", "a") - local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a") - local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a") - local set_name = "ipset" - if NFTFLAG == "1" then - set_name = "nftset" - end - for key, value in pairs(list1) do - if value.address then - local domain = "." .. key - if key == "#" then - domain = key - end - address_out:write(string.format("address=/%s/%s", domain, value.address) .. "\n") - end - if value.dns and #value.dns > 0 then - for i, dns in ipairs(value.dns) do - server_out:write(string.format("server=/.%s/%s", key, dns) .. "\n") - end - end - if value.ipsets and #value.ipsets > 0 then - local ipsets_str = "" - for i, ipset in ipairs(value.ipsets) do - ipsets_str = ipsets_str .. ipset .. "," - end - ipsets_str = ipsets_str:sub(1, #ipsets_str - 1) - ipset_out:write(string.format("%s=/.%s/%s", set_name, key, ipsets_str) .. "\n") - end - end - address_out:close() - server_out:close() - ipset_out:close() - end - - local f_out = io.open(CACHE_TEXT_FILE, "a") - f_out:write(new_text) - f_out:close() - end - - if USE_CHINADNS_NG == "0" then - api.remove(TMP_DNSMASQ_PATH) - fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH) - end - - if DNSMASQ_CONF_FILE ~= "nil" then - local conf_lines = {} - if LISTEN_PORT then - --Copy dnsmasq instance - conf_lines = copy_instance({["-LISTEN_PORT"] = LISTEN_PORT, ["-TMP_DNSMASQ_PATH"] = TMP_DNSMASQ_PATH, ["-return"] = "1"}) - --dhcp.leases to hosts - local hosts = api.CACHE_PATH .. "/dhcp-hosts" - sys.call("touch " .. hosts) - tinsert(conf_lines, "addn-hosts=" .. hosts) - else - --Modify the default dnsmasq service - end - if USE_CHINADNS_NG == "0" then - tinsert(conf_lines, string.format("conf-dir=%s", TMP_DNSMASQ_PATH)) - end - if dnsmasq_default_dns then - for s in string.gmatch(dnsmasq_default_dns, '[^' .. "," .. ']+') do - tinsert(conf_lines, string.format("server=%s", s)) - end - tinsert(conf_lines, "all-servers") - tinsert(conf_lines, "no-poll") - tinsert(conf_lines, "no-resolv") - if USE_CHINADNS_NG == "0" then - log(string.format(" - 默认:%s", dnsmasq_default_dns)) - end - - if FLAG == "default" then - api.set_cache_var("DEFAULT_DNS", DEFAULT_DNS) - end - end - if #conf_lines > 0 then - local conf_out = io.open(DNSMASQ_CONF_FILE, "a") - if conf_out then - conf_out:write(table.concat(conf_lines, "\n")) - conf_out:write("\n") - conf_out:close() - end - end - end - - if USE_CHINADNS_NG == "0" then - log(" - PassWall必须依赖于Dnsmasq,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理域名)分流失效!!!") - end -end - -_G.stretch = stretch -_G.restart = restart -_G.logic_restart = logic_restart -_G.copy_instance = copy_instance -_G.add_rule = add_rule - -if arg[1] then - local func =_G[arg[1]] - if func then - func(api.get_function_args(arg)) - end -end +local api = require "luci.passwall.api" +local sys = api.sys +local fs = api.fs +local datatypes = api.datatypes +local TMP = {} + +local function tinsert(table_name, val) + if table_name and type(table_name) == "table" then + if not TMP[table_name] then + TMP[table_name] = {} + end + if TMP[table_name][val] then + return false + end + table.insert(table_name, val) + TMP[table_name][val] = true + return true + end + return false +end + +local function backup_servers() + local DNSMASQ_DNS = api.uci_get("dhcp", "@dnsmasq[0]", "server") + if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then + api.uci_set_c("@global[0]", "dnsmasq_servers", DNSMASQ_DNS) + api.uci_save_c(true) + end +end + +local function restore_servers() + local dns_table = {} + local DNSMASQ_DNS = api.uci_get("dhcp", "@dnsmasq[0]", "server") + if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then + for k, v in ipairs(DNSMASQ_DNS) do + tinsert(dns_table, v) + end + end + local OLD_SERVER = api.uci_get(api.c_config, "@global[0]", "dnsmasq_servers") + if OLD_SERVER and #OLD_SERVER > 0 then + for k, v in ipairs(OLD_SERVER) do + tinsert(dns_table, v) + end + api.uci_del_c("@global[0]", "dnsmasq_servers") + api.uci_save_c(true) + end + if dns_table and #dns_table > 0 then + api.uci_set("dhcp", "@dnsmasq[0]", "server", dns_table) + api.uci_save(nil, "dhcp", true) + end +end + +function stretch() + local dnsmasq_server = api.uci_get("dhcp", "@dnsmasq[0]", "server") + local dnsmasq_noresolv = api.uci_get("dhcp", "@dnsmasq[0]", "noresolv") + local _flag + if dnsmasq_server and #dnsmasq_server > 0 then + for k, v in ipairs(dnsmasq_server) do + if not v:find("/") then + _flag = true + end + end + end + if not _flag and dnsmasq_noresolv == "1" then + api.uci_del("dhcp", "@dnsmasq[0]", "noresolv") + local RESOLVFILE = "/tmp/resolv.conf.d/resolv.conf.auto" + local file = io.open(RESOLVFILE, "r") + if not file then + RESOLVFILE = "/tmp/resolv.conf.auto" + else + local size = file:seek("end") + file:close() + if size == 0 then + RESOLVFILE = "/tmp/resolv.conf.auto" + end + end + api.uci_set("dhcp", "@dnsmasq[0]", "resolvfile", RESOLVFILE) + api.uci_save(nil, "dhcp", true) + end +end + +function restart(var) + local LOG = var["-LOG"] + sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") + if LOG == "1" then + api.log("重启 dnsmasq 服务") + end +end + +function logic_restart(var) + local LOG = var["-LOG"] + local DEFAULT_DNS = api.get_cache_var("DEFAULT_DNS") + if DEFAULT_DNS then + backup_servers() + --sys.call("sed -i '/list server/d' /etc/config/dhcp >/dev/null 2>&1") + local dns_table = {} + local dnsmasq_server = api.uci_get("dhcp", "@dnsmasq[0]", "server") + if dnsmasq_server and #dnsmasq_server > 0 then + for k, v in ipairs(dnsmasq_server) do + if v:find("/") then + tinsert(dns_table, v) + end + end + api.uci_set("dhcp", "@dnsmasq[0]", "server", dns_table) + api.uci_save(nil, "dhcp", true) + end + sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") + restore_servers() + else + sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") + end + if LOG == "1" then + api.log("重启 dnsmasq 服务") + end +end + +function copy_instance(var) + local LISTEN_PORT = var["-LISTEN_PORT"] + local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] + local conf_lines = {} + local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')") + for line in io.lines("/tmp/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do + local filter + if line:find("passwall") then filter = true end + if line:find("ubus") then filter = true end + if line:find("dhcp") then filter = true end + if line:find("server=") == 1 then filter = true end + if line:find("port=") == 1 then filter = true end + if line:find("conf%-dir=") == 1 then + filter = true + if TMP_DNSMASQ_PATH then + local tmp_path = line:sub(1 + #"conf-dir=") + sys.call(string.format("cp -r %s/* %s/ 2>/dev/null", tmp_path, TMP_DNSMASQ_PATH)) + end + end + if line:find("address=") == 1 or (line:find("server=") == 1 and line:find("/")) then filter = nil end + if not filter then + tinsert(conf_lines, line) + end + end + tinsert(conf_lines, "port=" .. LISTEN_PORT) + if TMP_DNSMASQ_PATH then + sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall*") + end + if var["-return"] == "1" then + return conf_lines + end + if #conf_lines > 0 then + local DNSMASQ_CONF = var["-DNSMASQ_CONF"] + local conf_out = io.open(DNSMASQ_CONF, "a") + conf_out:write(table.concat(conf_lines, "\n")) + conf_out:write("\n") + conf_out:close() + end +end + +function add_rule(var) + local FLAG = var["-FLAG"] + local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] + local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"] + local LISTEN_PORT = var["-LISTEN_PORT"] + local DEFAULT_DNS = var["-DEFAULT_DNS"] + local LOCAL_DNS = var["-LOCAL_DNS"] + local TUN_DNS = var["-TUN_DNS"] + local USE_DEFAULT_DNS = var["-USE_DEFAULT_DNS"] + local CHINADNS_DNS = var["-CHINADNS_DNS"] + local NODE = var["-NODE"] + local USE_DIRECT_LIST = var["-USE_DIRECT_LIST"] + local USE_PROXY_LIST = var["-USE_PROXY_LIST"] + local USE_BLOCK_LIST = var["-USE_BLOCK_LIST"] + local USE_GFW_LIST = var["-USE_GFW_LIST"] + local CHN_LIST = var["-CHN_LIST"] + local DEFAULT_PROXY_MODE = var["-DEFAULT_PROXY_MODE"] + local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"] + local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"] + local NFTFLAG = var["-NFTFLAG"] + local CACHE_PATH = api.CACHE_PATH + local CACHE_FLAG = "dnsmasq_" .. FLAG + local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG + local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt" + local USE_CHINADNS_NG = "0" + local IS_SHUNT_NODE = api.uci_get_c(NODE, "protocol") == "_shunt" + local USE_GEOVIEW = api.uci_get_c("@global_rules[0]", "enable_geoview") + + local list1 = {} + local excluded_domain = {} + + if not api.is_finded("geoview") then + USE_GEOVIEW = "0" + end + + local function log(...) + if NO_LOGIC_LOG == "1" then + return + end + api.log(...) + end + + local function check_dns(domain, dns) + if domain == "" or domain:find("#") then + return false + end + if not dns then + return + end + for k,v in ipairs(list1[domain].dns) do + if dns == v then + return true + end + end + return false + end + + local function check_ipset(domain, ipset) + if domain == "" or domain:find("#") then + return false + end + if not ipset then + return + end + for k,v in ipairs(list1[domain].ipsets) do + if ipset == v then + return true + end + end + return false + end + + local function set_domain_address(domain, address) + if domain == "" or domain:find("#") then + return + end + if not list1[domain] then + list1[domain] = { + dns = {}, + ipsets = {} + } + end + if not list1[domain].address then + list1[domain].address = address + end + end + + local function set_domain_dns(domain, dns) + if domain == "" or domain:find("#") then + return + end + if not dns then + return + end + if not list1[domain] then + list1[domain] = { + dns = {}, + ipsets = {} + } + end + for line in string.gmatch(dns, '[^' .. "," .. ']+') do + if not check_dns(domain, line) then + table.insert(list1[domain].dns, line) + end + end + end + + local function set_domain_ipset(domain, ipset) + if domain == "" or domain:find("#") then + return + end + if not ipset then + return + end + if not list1[domain] then + list1[domain] = { + dns = {}, + ipsets = {} + } + end + for line in string.gmatch(ipset, '[^' .. "," .. ']+') do + if not check_ipset(domain, line) then + table.insert(list1[domain].ipsets, line) + end + end + end + + local function add_excluded_domain(domain) + if domain == "" or domain:find("#") then + return + end + excluded_domain[domain] = true + end + + local function check_excluded_domain(domain) + if domain == "" or domain:find("#") then + return false + end + if excluded_domain[domain] then + return true + end + local pos = domain:find(".", 1, true) + while pos do + if excluded_domain[domain:sub(pos + 1)] then + return true + end + pos = domain:find(".", pos + 1, true) + end + return false + end + + local function foreach_geosite(list_arg, callback) + local geosite_path = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/" + geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat" + if not fs.access(geosite_path) then return end + local bin = api.finded_com("geoview") + if not (bin and list_arg) then return end + local cmd = string.format("%q -type geosite -action extract -input %q -list %q -lowmem=true", bin, geosite_path, list_arg) + local pipe = io.popen(cmd) + if not pipe then return end + for line in pipe:lines() do + if line ~= "" then callback(line) end + end + pipe:close() + end + + local cache_text = "" + local nodes_address_md5 = sys.exec("echo -n $(uci show passwall | grep '\\.address') | md5sum") + local new_rules = sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)") + local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. USE_DEFAULT_DNS .. CHINADNS_DNS .. USE_DIRECT_LIST .. USE_PROXY_LIST .. USE_BLOCK_LIST .. USE_GFW_LIST .. CHN_LIST .. DEFAULT_PROXY_MODE .. NO_PROXY_IPV6 .. nodes_address_md5 .. new_rules .. NFTFLAG + if fs.access(CACHE_TEXT_FILE) then + for line in io.lines(CACHE_TEXT_FILE) do + cache_text = line + end + end + + if cache_text ~= new_text then + api.remove(CACHE_DNS_PATH .. "*") + end + + local dnsmasq_default_dns + if USE_DEFAULT_DNS ~= "nil" then + if USE_DEFAULT_DNS == "direct" then + dnsmasq_default_dns = LOCAL_DNS + end + if USE_DEFAULT_DNS == "remote" then + dnsmasq_default_dns = TUN_DNS + end + if USE_DEFAULT_DNS == "remote" and CHN_LIST == "direct" then + dnsmasq_default_dns = TUN_DNS + end + end + + local only_global + if DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0" then + --没有启用中国列表和GFW列表时 + dnsmasq_default_dns = TUN_DNS + only_global = 1 + end + if USE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then + dnsmasq_default_dns = CHINADNS_DNS + USE_CHINADNS_NG = "1" + end + + local setflag_4= (NFTFLAG == "1") and "4#inet#passwall#" or "" + local setflag_6= (NFTFLAG == "1") and "6#inet#passwall#" or "" + + if not fs.access(CACHE_DNS_PATH) then + fs.mkdir(CACHE_DNS_PATH) + + --屏蔽列表 + if USE_CHINADNS_NG == "0" and USE_BLOCK_LIST == "1" then + local geosite_arg = "" + local f = io.open("/usr/share/passwall/rules/block_host") + if f then + for line in f:lines() do + if not line:find("#") and line:find("geosite:") then + line = string.match(line, ":([^:]+)$") + geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line + else + line = api.get_std_domain(line) + if line ~= "" and not line:find("#") and not line:find(":") then + set_domain_address(line, "") + end + end + end + f:close() + end + if USE_GEOVIEW == "1" and geosite_arg ~= "" then + foreach_geosite(geosite_arg, function(line) + set_domain_address(line, "") + end) + log(" - 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成") + end + end + + local fwd_dns + local no_ipv6 + + --始终用国内DNS解析节点域名 + if true then + fwd_dns = LOCAL_DNS + if USE_CHINADNS_NG == "1" then + fwd_dns = nil + else + local sets = { + setflag_4 .. "psw_vps", + setflag_6 .. "psw_vps6" + } + local function process_address(address) + address = (address or ""):lower() + if api.vps_domain_exclude(address) then return end + if datatypes.hostname(address) then + set_domain_dns(address, fwd_dns) + set_domain_ipset(address, table.concat(sets, ",")) + end + end + api.uci_foreach_c("nodes", function(t) + process_address(t.address) + process_address(t.download_address) + local dns, _ = api.get_domain_port_from_url(t.domain_resolver_dns or t.domain_resolver_dns_https or "") + if dns and dns ~= "" then + process_address(dns) + end + end) + api.uci_foreach_c("subscribe_list", function(t) --订阅链接 + local url, _ = api.get_domain_port_from_url(t.url or "") + if url and url ~= "" then + process_address(url) + end + end) + log(string.format(" - 节点列表中的域名(vpslist):%s", fwd_dns or "默认")) + end + end + + --直连(白名单)列表 + if USE_DIRECT_LIST == "1" then + fwd_dns = LOCAL_DNS + if USE_CHINADNS_NG == "1" then + fwd_dns = nil + end + if fwd_dns then + local sets = { + setflag_4 .. "psw_white", + setflag_6 .. "psw_white6" + } + --始终用国内DNS解析直连(白名单)列表 + local geosite_arg = "" + local f = io.open("/usr/share/passwall/rules/direct_host") + if f then + for line in f:lines() do + if not line:find("#") and line:find("geosite:") then + line = string.match(line, ":([^:]+)$") + geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line + else + line = api.get_std_domain(line) + if line ~= "" and not line:find("#") and not line:find(":") then + add_excluded_domain(line) + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end + end + end + f:close() + log(string.format(" - 域名白名单(whitelist):%s", fwd_dns or "默认")) + end + if USE_GEOVIEW == "1" and geosite_arg ~= "" then + foreach_geosite(geosite_arg, function(line) + add_excluded_domain(line) + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end) + log(" - 解析[直连列表] Geosite 到域名白名单(whitelist)完成") + end + end + end + + --代理(黑名单)列表 + if USE_PROXY_LIST == "1" then + fwd_dns = TUN_DNS + if USE_CHINADNS_NG == "1" then + fwd_dns = nil + end + if fwd_dns then + local set_name = "psw_black" + local set6_name = "psw_black6" + if FLAG ~= "default" then + set_name = "psw_" .. FLAG .. "_black" + set6_name = "psw_" .. FLAG .. "_black6" + end + local sets = { + setflag_4 .. set_name + } + if NO_PROXY_IPV6 ~= "1" then + table.insert(sets, setflag_6 .. set6_name) + end + --始终使用远程DNS解析代理(黑名单)列表 + local geosite_arg = "" + local f = io.open("/usr/share/passwall/rules/proxy_host") + if f then + for line in f:lines() do + if not line:find("#") and line:find("geosite:") then + line = string.match(line, ":([^:]+)$") + geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line + else + line = api.get_std_domain(line) + if line ~= "" and not line:find("#") and not line:find(":") then + add_excluded_domain(line) + if NO_PROXY_IPV6 == "1" then + set_domain_address(line, "::") + end + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end + end + end + f:close() + log(string.format(" - 代理域名表(blacklist):%s", fwd_dns or "默认")) + end + if USE_GEOVIEW == "1" and geosite_arg ~= "" then + foreach_geosite(geosite_arg, function(line) + add_excluded_domain(line) + if NO_PROXY_IPV6 == "1" then + set_domain_address(line, "::") + end + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end) + log(" - 解析[代理列表] Geosite 到代理域名表(blacklist)完成") + end + end + end + + --GFW列表 + if USE_GFW_LIST == "1" then + fwd_dns = TUN_DNS + if USE_CHINADNS_NG == "1" then + fwd_dns = nil + end + if fwd_dns then + local set_name = "psw_gfw" + local set6_name = "psw_gfw6" + if FLAG ~= "default" then + set_name = "psw_" .. FLAG .. "_gfw" + set6_name = "psw_" .. FLAG .. "_gfw6" + end + local sets = { + setflag_4 .. set_name + } + if NO_PROXY_IPV6 ~= "1" then + table.insert(sets, setflag_6 .. set6_name) + end + local f = io.open("/usr/share/passwall/rules/gfwlist") + if f then + for line in f:lines() do + if line ~= "" and not line:find("#") and not check_excluded_domain(line) then + if NO_PROXY_IPV6 == "1" then + set_domain_address(line, "::") + end + if dnsmasq_default_dns == fwd_dns then + fwd_dns = nil + else + set_domain_dns(line, fwd_dns) + end + set_domain_ipset(line, table.concat(sets, ",")) + end + end + f:close() + log(string.format(" - 防火墙域名表(gfwlist):%s", fwd_dns or "默认")) + end + end + end + + --中国列表 + if CHN_LIST ~= "0" then + fwd_dns = nil + if CHN_LIST == "direct" then + fwd_dns = LOCAL_DNS + end + if CHN_LIST == "proxy" then + fwd_dns = TUN_DNS + end + if USE_CHINADNS_NG == "1" then + fwd_dns = nil + end + if fwd_dns then + local sets = { + setflag_4 .. "psw_chn", + setflag_6 .. "psw_chn6" + } + if CHN_LIST == "proxy" then + if NO_PROXY_IPV6 == "1" then + sets = { + setflag_4 .. "psw_chn" + } + end + end + local f = io.open("/usr/share/passwall/rules/chnlist") + if f then + for line in f:lines() do + if line ~= "" and not line:find("#") and not check_excluded_domain(line) then + if CHN_LIST == "proxy" and NO_PROXY_IPV6 == "1" then + set_domain_address(line, "::") + end + if dnsmasq_default_dns == fwd_dns then + fwd_dns = nil + else + set_domain_dns(line, fwd_dns) + end + set_domain_ipset(line, table.concat(sets, ",")) + end + end + f:close() + log(string.format(" - 中国域名表(chnroute):%s", fwd_dns or "默认")) + end + end + end + + --分流规则 + if IS_SHUNT_NODE and USE_CHINADNS_NG == "0" then + local t = api.uci_get_c(NODE) + local default_node_id = t["default_node"] or "_direct" + api.uci_foreach_c("shunt_rules", function(s) + local _node_id = t[s[".name"]] + if _node_id and _node_id ~= "_blackhole" and t["shunt_group"] == s.group then + if _node_id == "_default" then + _node_id = default_node_id + end + + fwd_dns = nil + no_ipv6 = nil + + local sets = {} + + if _node_id == "_direct" then + fwd_dns = LOCAL_DNS + if USE_DIRECT_LIST == "1" then + table.insert(sets, setflag_4 .. "psw_white") + table.insert(sets, setflag_6 .. "psw_white6") + else + local set_name = "psw_shunt" + local set6_name = "psw_shunt6" + if FLAG ~= "default" then + set_name = "psw_" .. FLAG .. "_shunt" + set6_name = "psw_" .. FLAG .. "_shunt6" + end + table.insert(sets, setflag_4 .. set_name) + table.insert(sets, setflag_6 .. set6_name) + end + else + local set_name = "psw_shunt" + local set6_name = "psw_shunt6" + if FLAG ~= "default" then + set_name = "psw_" .. FLAG .. "_shunt" + set6_name = "psw_" .. FLAG .. "_shunt6" + end + fwd_dns = TUN_DNS + table.insert(sets, setflag_4 .. set_name) + if NO_PROXY_IPV6 ~= "1" then + table.insert(sets, setflag_6 .. set6_name) + else + no_ipv6 = true + end + end + + local domain_list = s.domain_list or "" + local geosite_arg = "" + for line in string.gmatch(domain_list, "[^\r\n]+") do + if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then + if line:find("geosite:") then + line = string.match(line, ":([^:]+)$") + geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line + else + if line:find("domain:") or line:find("full:") then + line = string.match(line, ":([^:]+)$") + end + line = api.get_std_domain(line) + add_excluded_domain(line) + + if no_ipv6 then + set_domain_address(line, "::") + end + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end + end + end + + if USE_GFW_LIST == "1" and CHN_LIST == "0" and USE_GEOVIEW == "1" and geosite_arg ~= "" then --仅GFW模式解析geosite + foreach_geosite(geosite_arg, function(line) + add_excluded_domain(line) + if no_ipv6 then + set_domain_address(line, "::") + end + set_domain_dns(line, fwd_dns) + set_domain_ipset(line, table.concat(sets, ",")) + end) + log(string.format(" - 解析分流规则(%s) Geosite 完成", s.remarks)) + end + + if _node_id ~= "_direct" then + log(string.format(" - Sing-Box/Xray分流规则(%s):%s", s.remarks, fwd_dns or "默认")) + end + end + end) + elseif only_global == 1 and NO_PROXY_IPV6 == "1" then + --节点:固定节点 + --代理模式:全局模式 + --过滤代理域名 IPv6:启用 + --禁止解析所有IPv6记录 + list1["#"] = { + dns = {}, + ipsets = {}, + address = "::" + } + end + + if list1 and next(list1) then + local address_out = io.open(CACHE_DNS_PATH .. "/000-address.conf", "a") + local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a") + local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a") + local set_name = "ipset" + if NFTFLAG == "1" then + set_name = "nftset" + end + for key, value in pairs(list1) do + if value.address then + local domain = "." .. key + if key == "#" then + domain = key + end + address_out:write(string.format("address=/%s/%s", domain, value.address) .. "\n") + end + if value.dns and #value.dns > 0 then + for i, dns in ipairs(value.dns) do + server_out:write(string.format("server=/.%s/%s", key, dns) .. "\n") + end + end + if value.ipsets and #value.ipsets > 0 then + local ipsets_str = "" + for i, ipset in ipairs(value.ipsets) do + ipsets_str = ipsets_str .. ipset .. "," + end + ipsets_str = ipsets_str:sub(1, #ipsets_str - 1) + ipset_out:write(string.format("%s=/.%s/%s", set_name, key, ipsets_str) .. "\n") + end + end + address_out:close() + server_out:close() + ipset_out:close() + end + + local f_out = io.open(CACHE_TEXT_FILE, "a") + f_out:write(new_text) + f_out:close() + end + + if USE_CHINADNS_NG == "0" then + api.remove(TMP_DNSMASQ_PATH) + fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH) + end + + if DNSMASQ_CONF_FILE ~= "nil" then + local conf_lines = {} + if LISTEN_PORT then + --Copy dnsmasq instance + conf_lines = copy_instance({["-LISTEN_PORT"] = LISTEN_PORT, ["-TMP_DNSMASQ_PATH"] = TMP_DNSMASQ_PATH, ["-return"] = "1"}) + --dhcp.leases to hosts + local hosts = api.CACHE_PATH .. "/dhcp-hosts" + sys.call("touch " .. hosts) + tinsert(conf_lines, "addn-hosts=" .. hosts) + else + --Modify the default dnsmasq service + end + if USE_CHINADNS_NG == "0" then + tinsert(conf_lines, string.format("conf-dir=%s", TMP_DNSMASQ_PATH)) + end + if dnsmasq_default_dns then + for s in string.gmatch(dnsmasq_default_dns, '[^' .. "," .. ']+') do + tinsert(conf_lines, string.format("server=%s", s)) + end + tinsert(conf_lines, "all-servers") + tinsert(conf_lines, "no-poll") + tinsert(conf_lines, "no-resolv") + if USE_CHINADNS_NG == "0" then + log(string.format(" - 默认:%s", dnsmasq_default_dns)) + end + + if FLAG == "default" then + api.set_cache_var("DEFAULT_DNS", DEFAULT_DNS) + end + end + if #conf_lines > 0 then + local conf_out = io.open(DNSMASQ_CONF_FILE, "a") + if conf_out then + conf_out:write(table.concat(conf_lines, "\n")) + conf_out:write("\n") + conf_out:close() + end + end + end + + if USE_CHINADNS_NG == "0" then + log(" - PassWall必须依赖于Dnsmasq,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理域名)分流失效!!!") + end +end + +_G.stretch = stretch +_G.restart = restart +_G.logic_restart = logic_restart +_G.copy_instance = copy_instance +_G.add_rule = add_rule + +if arg[1] then + local func =_G[arg[1]] + if func then + func(api.get_function_args(arg)) + end +end diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 045c2630..cdae9db6 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.8.20 -PKG_RELEASE:=99 +PKG_RELEASE:=100 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua index bc11424e..13d17fb0 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua @@ -6,13 +6,13 @@ local geo_dir = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/ local geosite_path = geo_dir .. "/geosite.dat" local geoip_path = geo_dir .. "/geoip.dat" if fs.access(geosite_path) and fs.access(geoip_path) then - f = SimpleForm(api.c_config) - f.reset = false - f.submit = false + f = SimpleForm(api.c_config) + f.reset = false + f.submit = false - local obj = Template(api.appname .. "/rule/geoview") - obj.api = api - f:append(obj) + local obj = Template(api.appname .. "/rule/geoview") + obj.api = api + f:append(obj) end return f diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua index af15398a..8c8382ce 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua @@ -25,25 +25,25 @@ o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") o.default = o.keylist[3] if true then - m:appendTemplate("/node_list/node_list") + m:appendTemplate("/node_list/node_list") - if luci.http.formvalue("cbi.submit") == "1" then - local group_order = {} - group_order = luci.http.formvaluetable("group.order") - if group_order then - for k, v in pairs(group_order) do - if v and v~= "" then - local new_order = {} - string.gsub(v, "[^" .. " " .. "]+", function(w) - new_order[#new_order + 1] = w - end) - for idx, name in ipairs(new_order) do - m.uci:reorder(m.config, name, idx - 1) - end - end - end - end - end + if luci.http.formvalue("cbi.submit") == "1" then + local group_order = {} + group_order = luci.http.formvaluetable("group.order") + if group_order then + for k, v in pairs(group_order) do + if v and v~= "" then + local new_order = {} + string.gsub(v, "[^" .. " " .. "]+", function(w) + new_order[#new_order + 1] = w + end) + for idx, name in ipairs(new_order) do + m.uci:reorder(m.config, name, idx - 1) + end + end + end + end + end end return api.return_map(m) 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 dd4ba20a..18b568ec 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 @@ -112,8 +112,8 @@ end o = s:option(DummyValue, "_update", translate("Manual subscription All")) o.rawhtml = true o.cfgvalue = function(self, section) - return string.format([[ - ]], + return string.format([[ + ]], translate("Manual subscription All")) end @@ -210,8 +210,8 @@ end o = s:option(DummyValue, "_update", translate("Manual subscription")) o.rawhtml = true o.cfgvalue = function(self, section) - return string.format([[ - ]], + return string.format([[ + ]], section, translate("Manual subscription")) end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua index c2fc0347..5f5e293a 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua @@ -1,976 +1,976 @@ -local singbox_bin = api.finded_com("sing-box") - -if not singbox_bin then - return -end - --- [[ sing-box ]] -local m, s1 = ... -local type_name = "sing-box" - -s1.fields["type"]:value(type_name, "Sing-Box") -if not s1.fields["type"].default then - s1.fields["type"].default = type_name -end - -if s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "singbox_" - -local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol") - -if formvalue_proto then s1.val["protocol"] = formvalue_proto end - -local arg_select_proto = luci.http.formvalue("select_proto") or "" - -local ss_method_new_list = { - "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" -} - -local ss_method_old_list = { - "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20", -} - -local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } - -local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") - -local singbox_version = api.get_app_version("sing-box"):match("[^v]+") - -o = s:option(ListValue, "protocol", translate("Protocol")) -o:value("socks", "Socks") -o:value("http", "HTTP") -o:value("shadowsocks", "Shadowsocks") -if singbox_tags:find("with_shadowsocksr") then - o:value("shadowsocksr", "ShadowsocksR") -end -o:value("vmess", "Vmess") -o:value("trojan", "Trojan") -if singbox_tags:find("with_wireguard") then - o:value("wireguard", "WireGuard") -end -if singbox_tags:find("with_quic") then - o:value("hysteria", "Hysteria") -end -o:value("vless", "VLESS") -if singbox_tags:find("with_quic") then - o:value("tuic", "TUIC") -end -if singbox_tags:find("with_quic") then - o:value("hysteria2", "Hysteria2") -end -o:value("anytls", "AnyTLS") -o:value("ssh", "SSH") -if singbox_tags:find("with_naive_outbound") then - o:value("naive", "NaïveProxy") -end -o:value("_urltest", translate("URLTest")) -o:value("_shunt", translate("Shunt")) -o:value("_iface", translate("Custom Interface")) -function o.custom_cfgvalue(self, section) - if arg_select_proto ~= "" then - return arg_select_proto - else - return m:get(section, self.config_option) - end -end - -local load_urltest_options = s1.val["protocol"] == "_urltest" or arg_select_proto == "_urltest" -local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" -local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" -local load_normal_options = true -if load_urltest_options or load_shunt_options or load_iface_options then - load_normal_options = nil -end -if not arg_select_proto:find("_") then - load_normal_options = true -end - -local netdev_list = api.get_network_devices() -local node_list = api.get_node_list() - -if load_urltest_options then -- [[ URLTest Start ]] - o = s:option(ListValue, "node_add_mode", translate("Node Addition Method")) - o:depends({ protocol = "_urltest" }) - o.default = "manual" - o:value("manual", translate("Manual")) - o:value("batch", translate("Batch")) - - o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, document")) - o:depends({ node_add_mode = "manual" }) - o.widget = "checkbox" - o.template = m:template_path("/cbi/nodes_multivalue") - o.group = {} - for k1, v1 in pairs(node_list) do - if k1 == "socks_list" or k1 == "normal_list" then - for i, v in ipairs(v1) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" - end - end - end - -- Reading the old DynamicList - function o.custom_cfgvalue(self, section) - return table.concat(m:get(section, "urltest_node") or {}, " ") - end - -- Write-and-hold DynamicList - function o.custom_write(self, section, value) - local old = m:get(section, "urltest_node") or {} - local new, set = {}, {} - for v in value:gmatch("%S+") do - new[#new + 1] = v - set[v] = 1 - end - for _, v in ipairs(old) do - if not set[v] then - m:set(section, "urltest_node", new) - return - end - set[v] = nil - end - for _ in pairs(set) do - m:set(section, "urltest_node", new) - return - end - end - - o = s:option(MultiValue, "node_group", translate("Select Group")) - o:depends({ node_add_mode = "batch" }) - o.widget = "checkbox" - o:value("default", translate("default")) - for k, v in pairs(groups) do - o:value(api.UrlEncode(k), k) - end - - o = s:option(Value, "node_match_rule", translate("Node Matching Rules")) - o:depends({ node_add_mode = "batch" }) - local descrStr = "Example: ^A && B && !C && D$
" - descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" - descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
" - descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
" - descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)." - o.description = translate(descrStr) - - o = s:option(Value, "urltest_url", translate("Probe URL")) - o:depends({ protocol = "_urltest" }) - o:value("https://cp.cloudflare.com/", "Cloudflare") - o:value("https://www.gstatic.com/generate_204", "Gstatic") - o:value("https://www.google.com/generate_204", "Google") - o:value("https://www.youtube.com/generate_204", "YouTube") - o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") - o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") - o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") - o.default = o.keylist[3] - o.description = translate("The URL used to detect the connection status.") - - o = s:option(Value, "urltest_interval", translate("Test interval")) - o:depends({ protocol = "_urltest" }) - o.default = "3m" - o.placeholder = "3m" - o.description = translate("The interval between initiating probes.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") .. "
" .. - translate("Test interval must be less or equal than idle timeout.") - - o = s:option(Value, "urltest_tolerance", translate("Test tolerance"), translate("The test tolerance in milliseconds.")) - o:depends({ protocol = "_urltest" }) - o.datatype = "uinteger" - o.placeholder = "50" - o.default = "50" - - o = s:option(Value, "urltest_idle_timeout", translate("Idle timeout")) - o:depends({ protocol = "_urltest" }) - o.placeholder = "30m" - o.default = "30m" - o.description = translate("The idle timeout.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") - - o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections")) - o:depends({ protocol = "_urltest" }) - o.default = "0" - o.description = translate("Interrupt existing connections when the selected outbound has changed.") -end -- [[ URLTest End ]] - -if load_iface_options then -- [[ Custom Interface Start ]] - o = s:option(Value, "iface", translate("Interface")) - o:depends({ protocol = "_iface" }) - for _, d in ipairs(netdev_list) do - o:value(d.name, d.label) - end -end -- [[ Custom Interface End ]] - - --- [[ Normal single node Start ]] -if load_normal_options then - -o = s:option(Value, "address", translate("Address (Support Domain Name)")) - -o = s:option(Value, "port", translate("Port")) -o.datatype = "port" - -o = s:option(Value, "uuid", translate("ID")) -o.password = true -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "tuic" }) - -o = s:option(Value, "username", translate("Username")) -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "ssh" }) -o:depends({ protocol = "naive" }) - -o = s:option(Value, "password", translate("Password")) -o.password = true -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "shadowsocksr" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "anytls" }) -o:depends({ protocol = "ssh" }) -o:depends({ protocol = "naive" }) - -o = s:option(ListValue, "security", translate("Encrypt Method")) -for a, t in ipairs(security_list) do o:value(t) end -o:depends({ protocol = "vmess" }) - -o = s:option(ListValue, "ss_method", translate("Encrypt Method")) -for a, t in ipairs(ss_method_new_list) do o:value(t) end -for a, t in ipairs(ss_method_old_list) do o:value(t) end -o:depends({ protocol = "shadowsocks" }) - -if singbox_tags:find("with_shadowsocksr") then - o = s:option(ListValue, "ssr_method", translate("Encrypt Method")) - for a, t in ipairs(ss_method_old_list) do o:value(t) end - o:depends({ protocol = "shadowsocksr" }) - - local ssr_protocol_list = { - "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", - "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", - "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", - "auth_chain_d", "auth_chain_e", "auth_chain_f" - } - - o = s:option(ListValue, "ssr_protocol", translate("Protocol")) - for a, t in ipairs(ssr_protocol_list) do o:value(t) end - o:depends({ protocol = "shadowsocksr" }) - - o = s:option(Value, "ssr_protocol_param", translate("Protocol_param")) - o:depends({ protocol = "shadowsocksr" }) - - local ssr_obfs_list = { - "plain", "http_simple", "http_post", "random_head", "tls_simple", - "tls1.0_session_auth", "tls1.2_ticket_auth" - } - - o = s:option(ListValue, "ssr_obfs", translate("Obfs")) - for a, t in ipairs(ssr_obfs_list) do o:value(t) end - o:depends({ protocol = "shadowsocksr" }) - - o = s:option(Value, "ssr_obfs_param", translate("Obfs_param")) - o:depends({ protocol = "shadowsocksr" }) -end - -o = s:option(Flag, "uot", translate("UDP over TCP")) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "naive" }) - -o = s:option(Value, "alter_id", "Alter ID") -o.datatype = "uinteger" -o.default = "0" -o:depends({ protocol = "vmess" }) - -o = s:option(Flag, "global_padding", "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled.")) -o.default = "0" -o:depends({ protocol = "vmess" }) - -o = s:option(Flag, "authenticated_length", "authenticated_length", translate("Protocol parameter. Enable length block encryption.")) -o.default = "0" -o:depends({ protocol = "vmess" }) - -o = s:option(ListValue, "flow", translate("flow")) -o.default = "" -o:value("", translate("Disable")) -o:value("xtls-rprx-vision") -o:depends({ protocol = "vless", tls = true }) - -if singbox_tags:find("with_quic") then - o = s:option(Value, "hysteria_hop", translate("Port hopping range")) - o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_hop_interval", translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)") - o.datatype = "uinteger" - o.placeholder = "30" - o.default = "30" - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_obfs", translate("Obfs Password")) - o:depends({ protocol = "hysteria" }) - - o = s:option(ListValue, "hysteria_auth_type", translate("Auth Type")) - o:value("disable", translate("Disable")) - o:value("string", translate("STRING")) - o:value("base64", translate("BASE64")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_auth_password", translate("Auth Password")) - o.password = true - o:depends({ protocol = "hysteria", hysteria_auth_type = "string"}) - o:depends({ protocol = "hysteria", hysteria_auth_type = "base64"}) - - o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps")) - o.default = "10" - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps")) - o.default = "50" - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection")) - o:depends({ protocol = "hysteria" }) -end - -if singbox_tags:find("with_quic") then - o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm")) - o.default = "cubic" - o:value("bbr", translate("BBR")) - o:value("cubic", translate("CUBIC")) - o:value("new_reno", translate("New Reno")) - o:depends({ protocol = "tuic" }) - - o = s:option(ListValue, "tuic_udp_relay_mode", translate("UDP relay mode")) - o.default = "native" - o:value("native", translate("native")) - o:value("quic", translate("QUIC")) - o:depends({ protocol = "tuic" }) - - --[[ - o = s:option(Flag, "tuic_udp_over_stream", translate("UDP over stream")) - o:depends({ protocol = "tuic" }) - ]]-- - - o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake")) - o.default = 0 - o:depends({ protocol = "tuic" }) - - o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)")) - o.datatype = "uinteger" - o.default = "3" - o:depends({ protocol = "tuic" }) - - o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN")) - o.default = "default" - o:value("default", translate("Default")) - o:value("h3") - o:value("h2") - o:value("h3,h2") - o:value("http/1.1") - o:value("h2,http/1.1") - o:value("h3,h2,http/1.1") - o:value("spdy/3.1") - o:value("h3,spdy/3.1") - o:depends({ protocol = "tuic" }) -end - -if singbox_tags:find("with_quic") then - o = s:option(Value, "hysteria2_hop", translate("Port hopping range")) - o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") - o:depends({ protocol = "hysteria2", hysteria2_realms = false }) - - o = s:option(Value, "hysteria2_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({ protocol = "hysteria2", hysteria2_realms = false }) - - o = s:option(Flag, "hysteria2_realms", translate("Realms")) - o.default = "0" - if api.compare_versions(singbox_version, ">=", "1.14.0") then - o:depends({ protocol = "hysteria2"}) - else - o:depends({ protocol = "__hide"}) - end - - o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") - o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) - o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } - o:depends({ hysteria2_realms = "1" }) - - o = s:option(Value, "hysteria2_auth_password", translate("Auth Password")) - o.password = true - o:depends({ protocol = "hysteria2"}) - - o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) - o:value("", translate("Disable")) - o:value("salamander") - o:value("gecko") - o:depends({ protocol = "hysteria2" }) - - o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) - o:depends({ hysteria2_obfs_type = "salamander" }) - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) - o.datatype = "uinteger" - o.placeholder = "512" - o.default = "512" - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) - o.datatype = "uinteger" - o.placeholder = "1200" - o.default = "1200" - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) - o:depends({ protocol = "hysteria2" }) - - o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) - o:depends({ protocol = "hysteria2" }) - - o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") - o.datatype = "range(4,120)" - o:depends({ protocol = "hysteria2"}) - - o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") - o.datatype = "range(2,60)" - o:depends({ protocol = "hysteria2"}) - - o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection")) - o.default = "0" - o:depends({ protocol = "hysteria2"}) -end - --- [[ SSH config start ]] -- -o = s:option(TextValue, "ssh_priv_key", translate("Private Key")) -o.rows = 5 -o.wrap = "off" -o:depends({ protocol = "ssh" }) -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") - return value -end - -o = s:option(Value, "ssh_priv_key_pp", translate("Private Key Passphrase")) -o.password = true -o:depends({ protocol = "ssh" }) - -o = s:option(DynamicList, "ssh_host_key", translate("Host Key"), translate("Accept any if empty.")) -o:depends({ protocol = "ssh" }) - -o = s:option(DynamicList, "ssh_host_key_algo", translate("Host Key Algorithms")) -o:depends({ protocol = "ssh" }) - -o = s:option(Value, "ssh_client_version", translate("Client Version"), translate("Random version will be used if empty.")) -o:depends({ protocol = "ssh" }) --- [[ SSH config end ]] -- - --- [[ naive start ]] -- -o = s:option(Value, "naive_insecure_concurrency", translate("Concurrent Tunnels")) -o.datatype = "uinteger" -o.placeholder = "0" -o.default = "0" -o:depends({ protocol = "naive" }) - -o = s:option(Flag, "naive_quic", translate("QUIC")) -o.default = 0 -o:depends({ protocol = "naive" }) - -o = s:option(ListValue, "naive_congestion_control", translate("Congestion control algorithm")) -o.default = "bbr" -o:value("bbr", translate("BBR")) -o:value("bbr2", translate("BBRv2")) -o:value("cubic", translate("CUBIC")) -o:value("reno", translate("New Reno")) -o:depends({ naive_quic = "1" }) --- [[ naive end ]] -- - -o = s:option(Flag, "tls", translate("TLS")) -o.default = 0 -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "http" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "anytls" }) - -o = s:option(ListValue, "alpn", translate("ALPN")) -o.default = "default" -o:value("default", translate("Default")) -o:value("h3") -o:value("h2") -o:value("h3,h2") -o:value("http/1.1") -o:value("h2,http/1.1") -o:value("h3,h2,http/1.1") -o:depends({ tls = true }) -o:depends({ protocol = "hysteria" }) - -o = s:option(Flag, "tls_disable_sni", translate("Disable SNI"), translate("Do not send server name in ClientHello.")) -o.default = "0" -o:depends({ tls = true }) -o:depends({ protocol = "hysteria"}) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain")) -o:depends({ tls = true }) -o:depends({ protocol = "hysteria"}) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) -o:depends({ protocol = "naive" }) - -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:depends({ tls = true }) -o:depends({ protocol = "hysteria"}) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)")) -o.default = "0" -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria"}) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) -o:depends({ protocol = "naive" }) - -o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format.")) -o.default = "" -o.rows = 5 -o.wrap = "off" -o:depends({ tls_certificate = true }) -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") - return value -end - -o = s:option(DynamicList, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites.")) -o:value("TLS_AES_128_GCM_SHA256") -o:value("TLS_AES_256_GCM_SHA384") -o:value("TLS_CHACHA20_POLY1305_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") -o:value("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") -o:value("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") -o:value("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") -o:value("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") -o:value("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") -o:value("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") -o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") -o:depends({ tls = true }) - -o = s:option(Flag, "ech", translate("ECH")) -o.default = "0" -o:depends({ tls = true, flow = "", reality = false }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria" }) -o:depends({ protocol = "hysteria2", hysteria2_realms = false }) -o:depends({ protocol = "naive" }) - -o = s:option(TextValue, "ech_config", translate("ECH Config")) -o.default = "" -o.rows = 5 -o.wrap = "off" -o:depends({ ech = true }) -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") - return value -end - -o = s:option(Value, "ech_query_server_name", translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries.")) -o:depends({ ech = true }) - -if singbox_tags:find("with_utls") then - o = s:option(Flag, "utls", translate("uTLS")) - o.default = "0" - o:depends({ tls = true }) - - o = s:option(ListValue, "fingerprint", translate("Finger Print")) - o:value("chrome") - o:value("firefox") - o:value("edge") - o:value("safari") - o:value("360") - o:value("qq") - o:value("ios") - o:value("android") - o:value("random") - o:value("randomized") - o.default = "chrome" - o:depends({ utls = true }) - - -- [[ REALITY ]] -- - o = s:option(Flag, "reality", translate("REALITY")) - o.default = 0 - o:depends({ protocol = "vless", tls = true }) - o:depends({ protocol = "vmess", tls = true }) - o:depends({ protocol = "shadowsocks", tls = true }) - o:depends({ protocol = "socks", tls = true }) - o:depends({ protocol = "trojan", tls = true }) - o:depends({ protocol = "anytls", tls = true }) - - o = s:option(Value, "reality_publicKey", translate("Public Key")) - o:depends({ reality = true }) - - o = s:option(Value, "reality_shortId", translate("Short Id")) - o:depends({ reality = true }) -end - -o = s:option(Flag, "anytls_disable_reuse", translate("Disable TLS Reuse")) -o.default = 0 -o:depends({ protocol = "anytls" }) - -o = s:option(ListValue, "transport", translate("Transport")) -o:value("tcp", "TCP") -o:value("http", "HTTP") -o:value("ws", "WebSocket") -o:value("httpupgrade", "HTTPUpgrade") -if singbox_tags:find("with_quic") then - o:value("quic", "QUIC") -end -if singbox_tags:find("with_grpc") then - o:value("grpc", "gRPC") -else o:value("grpc", "gRPC-lite") -end -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - -if singbox_tags:find("with_wireguard") then - o = s:option(Value, "wireguard_public_key", translate("Public Key")) - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_secret_key", translate("Private Key")) - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key")) - o:depends({ protocol = "wireguard" }) - - o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_mtu", translate("MTU")) - o.default = "1420" - o:depends({ protocol = "wireguard" }) - - o = s:option(Flag, "wireguard_system_interface", translate("System interface")) - o.default = 0 - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_interface_name", translate("System interface name")) - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) - o:depends({ protocol = "wireguard" }) -end - --- [[ TCP ]]-- -o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) -o:value("none", "none") -o:value("http", "http") -o:depends({ transport = "tcp" }) - -o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) -o:depends({ tcp_guise = "http" }) - -o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) -o.placeholder = "/" -o:depends({ tcp_guise = "http" }) - --- [[ HTTP ]]-- -o = s:option(DynamicList, "http_host", translate("HTTP Host")) -o:depends({ transport = "http" }) - -o = s:option(Value, "http_path", translate("HTTP Path")) -o.placeholder = "/" -o:depends({ transport = "http" }) - -o = s:option(Flag, "http_h2_health_check", translate("Health check")) -o:depends({ tls = true, transport = "http" }) - -o = s:option(Value, "http_h2_read_idle_timeout", translate("Idle timeout")) -o.default = "15" -o:depends({ http_h2_health_check = true }) - -o = s:option(Value, "http_h2_health_check_timeout", translate("Health check timeout")) -o.default = "15" -o:depends({ http_h2_health_check = true }) - --- [[ WebSocket ]]-- -o = s:option(Value, "ws_host", translate("WebSocket Host")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_path", translate("WebSocket Path")) -o.placeholder = "/" -o:depends({ transport = "ws" }) - -o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_maxEarlyData", translate("Early data length")) -o.default = "1024" -o:depends({ ws_enableEarlyData = true }) - -o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) -o:depends({ ws_enableEarlyData = true }) - --- [[ HTTPUpgrade ]]-- -o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) -o:depends({ transport = "httpupgrade" }) - -o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path")) -o.placeholder = "/" -o:depends({ transport = "httpupgrade" }) - --- [[ gRPC ]]-- -o = s:option(Value, "grpc_serviceName", "ServiceName") -o:depends({ transport = "grpc" }) - -o = s:option(Flag, "grpc_health_check", translate("Health check")) -o:depends({ transport = "grpc" }) - -o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout")) -o.default = "15" -o:depends({ grpc_health_check = true }) - -o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout")) -o.default = "15" -o:depends({ grpc_health_check = true }) - -o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream")) -o.default = "0" -o:depends({ grpc_health_check = true }) - --- [[ User-Agent ]]-- -o = s:option(Value, "user_agent", translate("User-Agent")) -o.default = "" -o:value("", translate("default")) -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome") -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox") -o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari") -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge") -o:value("Go-http-client/1.1", "golang") -o:value("curl/7.68.0", "curl") -o:depends({ tcp_guise = "http" }) -o:depends({ transport = "http" }) -o:depends({ transport = "ws" }) -o:depends({ transport = "httpupgrade" }) -o:depends({ protocol = "naive" }) - --- [[ Mux ]]-- -o = s:option(Flag, "mux", translate("Mux")) -o.rmempty = false -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless", flow = "" }) -o:depends({ protocol = "shadowsocks", uot = "" }) -o:depends({ protocol = "trojan" }) - -o = s:option(ListValue, "mux_type", translate("Mux")) -o:value("smux") -o:value("yamux") -o:value("h2mux") -o:depends({ mux = true }) - -o = s:option(Value, "mux_concurrency", translate("Mux concurrency")) -o.default = 4 -o:depends({ mux = true, tcpbrutal = false }) - -o = s:option(Flag, "mux_padding", translate("Padding")) -o.default = 0 -o:depends({ mux = true }) - --- [[ TCP Brutal ]]-- -o = s:option(Flag, "tcpbrutal", translate("TCP Brutal")) -o.default = 0 -o:depends({ mux = true }) - -o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps")) -o.default = "10" -o:depends({ tcpbrutal = true }) - -o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps")) -o.default = "50" -o:depends({ tcpbrutal = true }) - -o = s:option(Flag, "shadowtls", "ShadowTLS") -o.default = 0 -o:depends({ protocol = "vmess", tls = false }) -o:depends({ protocol = "shadowsocks", tls = false }) - -o = s:option(ListValue, "shadowtls_version", "ShadowTLS " .. translate("Version")) -o.default = "1" -o:value("1", "ShadowTLS v1") -o:value("2", "ShadowTLS v2") -o:value("3", "ShadowTLS v3") -o:depends({ shadowtls = true }) - -o = s:option(Value, "shadowtls_password", "ShadowTLS " .. translate("Password")) -o.password = true -o:depends({ shadowtls = true, shadowtls_version = "2" }) -o:depends({ shadowtls = true, shadowtls_version = "3" }) - -o = s:option(Value, "shadowtls_serverName", "ShadowTLS " .. translate("Domain")) -o:depends({ shadowtls = true }) - -if singbox_tags:find("with_utls") then - o = s:option(Flag, "shadowtls_utls", "ShadowTLS " .. translate("uTLS")) - o.default = "0" - o:depends({ shadowtls = true }) - - o = s:option(ListValue, "shadowtls_fingerprint", "ShadowTLS " .. translate("Finger Print")) - o:value("chrome") - o:value("firefox") - o:value("edge") - o:value("safari") - -- o:value("360") - o:value("qq") - o:value("ios") - -- o:value("android") - o:value("random") - -- o:value("randomized") - o.default = "chrome" - o:depends({ shadowtls = true, shadowtls_utls = true }) -end - --- [[ SIP003 plugin ]]-- -o = s:option(Flag, "plugin_enabled", translate("plugin")) -o.default = 0 -o:depends({ protocol = "shadowsocks" }) - -o = s:option(ListValue, "plugin", "SIP003 " .. translate("plugin")) -o.default = "obfs-local" -o:depends({ plugin_enabled = true }) -o:value("obfs-local") -o:value("v2ray-plugin") - -o = s:option(Value, "plugin_opts", translate("opts")) -o:depends({ plugin_enabled = true }) - -o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution.")) -o:value("", translate("Auto")) -o:value("tcp", "TCP") -o:value("udp", "UDP") -o:value("https", "HTTPS") - -o = s:option(Value, "domain_resolver_dns", "DNS") -o.datatype = "or(ipaddr,ipaddrport)" -o:value("114.114.114.114") -o:value("223.5.5.5:53") -o.default = o.keylist[1] -o:depends({ domain_resolver = "tcp" }) -o:depends({ domain_resolver = "udp" }) - -o = s:option(Value, "domain_resolver_dns_https", "DNS") -o:value("https://120.53.53.53/dns-query", "DNSPod") -o:value("https://223.5.5.5/dns-query", "AliDNS") -o.default = o.keylist[1] -o:depends({ domain_resolver = "https" }) - -o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect.")) -o.default = "" -o:value("", translate("Auto")) -o:value("prefer_ipv4", translate("Prefer IPv4")) -o:value("prefer_ipv6", translate("Prefer IPv6")) -o:value("ipv4_only", translate("IPv4 Only")) -o:value("ipv6_only", translate("IPv6 Only")) - -local protocols = s.fields["protocol"].keylist -if #protocols > 0 then - for i, v in ipairs(protocols) do - if not v:find("^_") then - local depends_condition = { protocol = v } - if v == "hysteria2" then - depends_condition["hysteria2_realms"] = false - end - s.fields["address"]:depends(depends_condition) - s.fields["port"]:depends(depends_condition) - s.fields["domain_resolver"]:depends(depends_condition) - s.fields["domain_strategy"]:depends(depends_condition) - end - end -end -end --- [[ Normal single node End ]] - -if not load_shunt_options then - o = s:option(ListValue, "chain_proxy", translate("Chain Proxy")) - o:value("", translate("Close(Not use)")) - if not (load_iface_options or load_urltest_options) then - -- Special node cannot be use pre-proxy. - o:value("1", translate("Preproxy Node")) - o:value("3", translate("Outbound Interface")) - end - o:value("2", translate("Landing Node")) - - o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy.")) - o1:depends({ chain_proxy = "1", hysteria2_realms = false }) - o1.template = m:template_path("/cbi/nodes_listvalue") - o1.group = {} - - o3 = s:option(Value, "outbound_iface", translate("Outbound Interface")) - o3:depends({ chain_proxy = "3" }) - o3:value("", translate("All")) - for _, d in ipairs(netdev_list) do - o3:value(d.name, d.label) - end - - o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy.")) - o2:depends({ chain_proxy = "2", hysteria2_realms = false }) - o2.template = m:template_path("/cbi/nodes_listvalue") - o2.group = {} - - for k1, v1 in pairs(node_list) do - if k1 ~= "shunt_list" and k1 ~= "iface_list" then - for i, v in ipairs(v1) do - if v.id ~= arg[1] then - o1:value(v.id, v.remark) - o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if k1 == "normal_list" then - -- Landing Node not support use special node. - o2:value(v.id, v.remark) - o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - end - end - end - end -end - -api.luci_types(s1, s) - -if load_shunt_options then - local current_node = m:get(arg[1]) or {} - local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") - setfenv(shunt_lua, getfenv(1))(m, s1, { - node_id = arg[1], - node = current_node, - node_list = node_list, - }) -end +local singbox_bin = api.finded_com("sing-box") + +if not singbox_bin then + return +end + +-- [[ sing-box ]] +local m, s1 = ... +local type_name = "sing-box" + +s1.fields["type"]:value(type_name, "Sing-Box") +if not s1.fields["type"].default then + s1.fields["type"].default = type_name +end + +if s1.val["type"] ~= type_name then + return +end + +local s = NamedSection(m, arg[1], "server") +s.type_name = type_name +s.option_prefix = "singbox_" + +local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol") + +if formvalue_proto then s1.val["protocol"] = formvalue_proto end + +local arg_select_proto = luci.http.formvalue("select_proto") or "" + +local ss_method_new_list = { + "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +local ss_method_old_list = { + "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20", +} + +local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } + +local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") + +local singbox_version = api.get_app_version("sing-box"):match("[^v]+") + +o = s:option(ListValue, "protocol", translate("Protocol")) +o:value("socks", "Socks") +o:value("http", "HTTP") +o:value("shadowsocks", "Shadowsocks") +if singbox_tags:find("with_shadowsocksr") then + o:value("shadowsocksr", "ShadowsocksR") +end +o:value("vmess", "Vmess") +o:value("trojan", "Trojan") +if singbox_tags:find("with_wireguard") then + o:value("wireguard", "WireGuard") +end +if singbox_tags:find("with_quic") then + o:value("hysteria", "Hysteria") +end +o:value("vless", "VLESS") +if singbox_tags:find("with_quic") then + o:value("tuic", "TUIC") +end +if singbox_tags:find("with_quic") then + o:value("hysteria2", "Hysteria2") +end +o:value("anytls", "AnyTLS") +o:value("ssh", "SSH") +if singbox_tags:find("with_naive_outbound") then + o:value("naive", "NaïveProxy") +end +o:value("_urltest", translate("URLTest")) +o:value("_shunt", translate("Shunt")) +o:value("_iface", translate("Custom Interface")) +function o.custom_cfgvalue(self, section) + if arg_select_proto ~= "" then + return arg_select_proto + else + return m:get(section, self.config_option) + end +end + +local load_urltest_options = s1.val["protocol"] == "_urltest" or arg_select_proto == "_urltest" +local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" +local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" +local load_normal_options = true +if load_urltest_options or load_shunt_options or load_iface_options then + load_normal_options = nil +end +if not arg_select_proto:find("_") then + load_normal_options = true +end + +local netdev_list = api.get_network_devices() +local node_list = api.get_node_list() + +if load_urltest_options then -- [[ URLTest Start ]] + o = s:option(ListValue, "node_add_mode", translate("Node Addition Method")) + o:depends({ protocol = "_urltest" }) + o.default = "manual" + o:value("manual", translate("Manual")) + o:value("batch", translate("Batch")) + + o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, document")) + o:depends({ node_add_mode = "manual" }) + o.widget = "checkbox" + o.template = m:template_path("/cbi/nodes_multivalue") + o.group = {} + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + end + end + -- Reading the old DynamicList + function o.custom_cfgvalue(self, section) + return table.concat(m:get(section, "urltest_node") or {}, " ") + end + -- Write-and-hold DynamicList + function o.custom_write(self, section, value) + local old = m:get(section, "urltest_node") or {} + local new, set = {}, {} + for v in value:gmatch("%S+") do + new[#new + 1] = v + set[v] = 1 + end + for _, v in ipairs(old) do + if not set[v] then + m:set(section, "urltest_node", new) + return + end + set[v] = nil + end + for _ in pairs(set) do + m:set(section, "urltest_node", new) + return + end + end + + o = s:option(MultiValue, "node_group", translate("Select Group")) + o:depends({ node_add_mode = "batch" }) + o.widget = "checkbox" + o:value("default", translate("default")) + for k, v in pairs(groups) do + o:value(api.UrlEncode(k), k) + end + + o = s:option(Value, "node_match_rule", translate("Node Matching Rules")) + o:depends({ node_add_mode = "batch" }) + local descrStr = "Example: ^A && B && !C && D$
" + descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" + descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
" + descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
" + descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)." + o.description = translate(descrStr) + + o = s:option(Value, "urltest_url", translate("Probe URL")) + o:depends({ protocol = "_urltest" }) + o:value("https://cp.cloudflare.com/", "Cloudflare") + o:value("https://www.gstatic.com/generate_204", "Gstatic") + o:value("https://www.google.com/generate_204", "Google") + o:value("https://www.youtube.com/generate_204", "YouTube") + o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") + o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") + o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") + o.default = o.keylist[3] + o.description = translate("The URL used to detect the connection status.") + + o = s:option(Value, "urltest_interval", translate("Test interval")) + o:depends({ protocol = "_urltest" }) + o.default = "3m" + o.placeholder = "3m" + o.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") .. "
" .. + translate("Test interval must be less or equal than idle timeout.") + + o = s:option(Value, "urltest_tolerance", translate("Test tolerance"), translate("The test tolerance in milliseconds.")) + o:depends({ protocol = "_urltest" }) + o.datatype = "uinteger" + o.placeholder = "50" + o.default = "50" + + o = s:option(Value, "urltest_idle_timeout", translate("Idle timeout")) + o:depends({ protocol = "_urltest" }) + o.placeholder = "30m" + o.default = "30m" + o.description = translate("The idle timeout.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") + + o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections")) + o:depends({ protocol = "_urltest" }) + o.default = "0" + o.description = translate("Interrupt existing connections when the selected outbound has changed.") +end -- [[ URLTest End ]] + +if load_iface_options then -- [[ Custom Interface Start ]] + o = s:option(Value, "iface", translate("Interface")) + o:depends({ protocol = "_iface" }) + for _, d in ipairs(netdev_list) do + o:value(d.name, d.label) + end +end -- [[ Custom Interface End ]] + + +-- [[ Normal single node Start ]] +if load_normal_options then + +o = s:option(Value, "address", translate("Address (Support Domain Name)")) + +o = s:option(Value, "port", translate("Port")) +o.datatype = "port" + +o = s:option(Value, "uuid", translate("ID")) +o.password = true +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "tuic" }) + +o = s:option(Value, "username", translate("Username")) +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "ssh" }) +o:depends({ protocol = "naive" }) + +o = s:option(Value, "password", translate("Password")) +o.password = true +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "shadowsocksr" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "anytls" }) +o:depends({ protocol = "ssh" }) +o:depends({ protocol = "naive" }) + +o = s:option(ListValue, "security", translate("Encrypt Method")) +for a, t in ipairs(security_list) do o:value(t) end +o:depends({ protocol = "vmess" }) + +o = s:option(ListValue, "ss_method", translate("Encrypt Method")) +for a, t in ipairs(ss_method_new_list) do o:value(t) end +for a, t in ipairs(ss_method_old_list) do o:value(t) end +o:depends({ protocol = "shadowsocks" }) + +if singbox_tags:find("with_shadowsocksr") then + o = s:option(ListValue, "ssr_method", translate("Encrypt Method")) + for a, t in ipairs(ss_method_old_list) do o:value(t) end + o:depends({ protocol = "shadowsocksr" }) + + local ssr_protocol_list = { + "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", + "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", + "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", + "auth_chain_d", "auth_chain_e", "auth_chain_f" + } + + o = s:option(ListValue, "ssr_protocol", translate("Protocol")) + for a, t in ipairs(ssr_protocol_list) do o:value(t) end + o:depends({ protocol = "shadowsocksr" }) + + o = s:option(Value, "ssr_protocol_param", translate("Protocol_param")) + o:depends({ protocol = "shadowsocksr" }) + + local ssr_obfs_list = { + "plain", "http_simple", "http_post", "random_head", "tls_simple", + "tls1.0_session_auth", "tls1.2_ticket_auth" + } + + o = s:option(ListValue, "ssr_obfs", translate("Obfs")) + for a, t in ipairs(ssr_obfs_list) do o:value(t) end + o:depends({ protocol = "shadowsocksr" }) + + o = s:option(Value, "ssr_obfs_param", translate("Obfs_param")) + o:depends({ protocol = "shadowsocksr" }) +end + +o = s:option(Flag, "uot", translate("UDP over TCP")) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "naive" }) + +o = s:option(Value, "alter_id", "Alter ID") +o.datatype = "uinteger" +o.default = "0" +o:depends({ protocol = "vmess" }) + +o = s:option(Flag, "global_padding", "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled.")) +o.default = "0" +o:depends({ protocol = "vmess" }) + +o = s:option(Flag, "authenticated_length", "authenticated_length", translate("Protocol parameter. Enable length block encryption.")) +o.default = "0" +o:depends({ protocol = "vmess" }) + +o = s:option(ListValue, "flow", translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ protocol = "vless", tls = true }) + +if singbox_tags:find("with_quic") then + o = s:option(Value, "hysteria_hop", translate("Port hopping range")) + o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_hop_interval", translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)") + o.datatype = "uinteger" + o.placeholder = "30" + o.default = "30" + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_obfs", translate("Obfs Password")) + o:depends({ protocol = "hysteria" }) + + o = s:option(ListValue, "hysteria_auth_type", translate("Auth Type")) + o:value("disable", translate("Disable")) + o:value("string", translate("STRING")) + o:value("base64", translate("BASE64")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_auth_password", translate("Auth Password")) + o.password = true + o:depends({ protocol = "hysteria", hysteria_auth_type = "string"}) + o:depends({ protocol = "hysteria", hysteria_auth_type = "base64"}) + + o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps")) + o.default = "10" + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps")) + o.default = "50" + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection")) + o:depends({ protocol = "hysteria" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm")) + o.default = "cubic" + o:value("bbr", translate("BBR")) + o:value("cubic", translate("CUBIC")) + o:value("new_reno", translate("New Reno")) + o:depends({ protocol = "tuic" }) + + o = s:option(ListValue, "tuic_udp_relay_mode", translate("UDP relay mode")) + o.default = "native" + o:value("native", translate("native")) + o:value("quic", translate("QUIC")) + o:depends({ protocol = "tuic" }) + + --[[ + o = s:option(Flag, "tuic_udp_over_stream", translate("UDP over stream")) + o:depends({ protocol = "tuic" }) + ]]-- + + o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake")) + o.default = 0 + o:depends({ protocol = "tuic" }) + + o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)")) + o.datatype = "uinteger" + o.default = "3" + o:depends({ protocol = "tuic" }) + + o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN")) + o.default = "default" + o:value("default", translate("Default")) + o:value("h3") + o:value("h2") + o:value("h3,h2") + o:value("http/1.1") + o:value("h2,http/1.1") + o:value("h3,h2,http/1.1") + o:value("spdy/3.1") + o:value("h3,spdy/3.1") + o:depends({ protocol = "tuic" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(Value, "hysteria2_hop", translate("Port hopping range")) + o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") + o:depends({ protocol = "hysteria2", hysteria2_realms = false }) + + o = s:option(Value, "hysteria2_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({ protocol = "hysteria2", hysteria2_realms = false }) + + o = s:option(Flag, "hysteria2_realms", translate("Realms")) + o.default = "0" + if api.compare_versions(singbox_version, ">=", "1.14.0") then + o:depends({ protocol = "hysteria2"}) + else + o:depends({ protocol = "__hide"}) + end + + o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") + o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) + o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } + o:depends({ hysteria2_realms = "1" }) + + o = s:option(Value, "hysteria2_auth_password", translate("Auth Password")) + o.password = true + o:depends({ protocol = "hysteria2"}) + + o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) + o:value("", translate("Disable")) + o:value("salamander") + o:value("gecko") + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) + o:depends({ hysteria2_obfs_type = "salamander" }) + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) + o.datatype = "uinteger" + o.placeholder = "512" + o.default = "512" + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) + o.datatype = "uinteger" + o.placeholder = "1200" + o.default = "1200" + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") + o.datatype = "range(4,120)" + o:depends({ protocol = "hysteria2"}) + + o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") + o.datatype = "range(2,60)" + o:depends({ protocol = "hysteria2"}) + + o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection")) + o.default = "0" + o:depends({ protocol = "hysteria2"}) +end + +-- [[ SSH config start ]] -- +o = s:option(TextValue, "ssh_priv_key", translate("Private Key")) +o.rows = 5 +o.wrap = "off" +o:depends({ protocol = "ssh" }) +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") + return value +end + +o = s:option(Value, "ssh_priv_key_pp", translate("Private Key Passphrase")) +o.password = true +o:depends({ protocol = "ssh" }) + +o = s:option(DynamicList, "ssh_host_key", translate("Host Key"), translate("Accept any if empty.")) +o:depends({ protocol = "ssh" }) + +o = s:option(DynamicList, "ssh_host_key_algo", translate("Host Key Algorithms")) +o:depends({ protocol = "ssh" }) + +o = s:option(Value, "ssh_client_version", translate("Client Version"), translate("Random version will be used if empty.")) +o:depends({ protocol = "ssh" }) +-- [[ SSH config end ]] -- + +-- [[ naive start ]] -- +o = s:option(Value, "naive_insecure_concurrency", translate("Concurrent Tunnels")) +o.datatype = "uinteger" +o.placeholder = "0" +o.default = "0" +o:depends({ protocol = "naive" }) + +o = s:option(Flag, "naive_quic", translate("QUIC")) +o.default = 0 +o:depends({ protocol = "naive" }) + +o = s:option(ListValue, "naive_congestion_control", translate("Congestion control algorithm")) +o.default = "bbr" +o:value("bbr", translate("BBR")) +o:value("bbr2", translate("BBRv2")) +o:value("cubic", translate("CUBIC")) +o:value("reno", translate("New Reno")) +o:depends({ naive_quic = "1" }) +-- [[ naive end ]] -- + +o = s:option(Flag, "tls", translate("TLS")) +o.default = 0 +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "http" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "anytls" }) + +o = s:option(ListValue, "alpn", translate("ALPN")) +o.default = "default" +o:value("default", translate("Default")) +o:value("h3") +o:value("h2") +o:value("h3,h2") +o:value("http/1.1") +o:value("h2,http/1.1") +o:value("h3,h2,http/1.1") +o:depends({ tls = true }) +o:depends({ protocol = "hysteria" }) + +o = s:option(Flag, "tls_disable_sni", translate("Disable SNI"), translate("Do not send server name in ClientHello.")) +o.default = "0" +o:depends({ tls = true }) +o:depends({ protocol = "hysteria"}) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain")) +o:depends({ tls = true }) +o:depends({ protocol = "hysteria"}) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) +o:depends({ protocol = "naive" }) + +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:depends({ tls = true }) +o:depends({ protocol = "hysteria"}) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)")) +o.default = "0" +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria"}) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) +o:depends({ protocol = "naive" }) + +o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format.")) +o.default = "" +o.rows = 5 +o.wrap = "off" +o:depends({ tls_certificate = true }) +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") + return value +end + +o = s:option(DynamicList, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites.")) +o:value("TLS_AES_128_GCM_SHA256") +o:value("TLS_AES_256_GCM_SHA384") +o:value("TLS_CHACHA20_POLY1305_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") +o:value("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") +o:value("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") +o:value("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") +o:value("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") +o:value("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") +o:value("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") +o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") +o:depends({ tls = true }) + +o = s:option(Flag, "ech", translate("ECH")) +o.default = "0" +o:depends({ tls = true, flow = "", reality = false }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria" }) +o:depends({ protocol = "hysteria2", hysteria2_realms = false }) +o:depends({ protocol = "naive" }) + +o = s:option(TextValue, "ech_config", translate("ECH Config")) +o.default = "" +o.rows = 5 +o.wrap = "off" +o:depends({ ech = true }) +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") + return value +end + +o = s:option(Value, "ech_query_server_name", translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries.")) +o:depends({ ech = true }) + +if singbox_tags:find("with_utls") then + o = s:option(Flag, "utls", translate("uTLS")) + o.default = "0" + o:depends({ tls = true }) + + o = s:option(ListValue, "fingerprint", translate("Finger Print")) + o:value("chrome") + o:value("firefox") + o:value("edge") + o:value("safari") + o:value("360") + o:value("qq") + o:value("ios") + o:value("android") + o:value("random") + o:value("randomized") + o.default = "chrome" + o:depends({ utls = true }) + + -- [[ REALITY ]] -- + o = s:option(Flag, "reality", translate("REALITY")) + o.default = 0 + o:depends({ protocol = "vless", tls = true }) + o:depends({ protocol = "vmess", tls = true }) + o:depends({ protocol = "shadowsocks", tls = true }) + o:depends({ protocol = "socks", tls = true }) + o:depends({ protocol = "trojan", tls = true }) + o:depends({ protocol = "anytls", tls = true }) + + o = s:option(Value, "reality_publicKey", translate("Public Key")) + o:depends({ reality = true }) + + o = s:option(Value, "reality_shortId", translate("Short Id")) + o:depends({ reality = true }) +end + +o = s:option(Flag, "anytls_disable_reuse", translate("Disable TLS Reuse")) +o.default = 0 +o:depends({ protocol = "anytls" }) + +o = s:option(ListValue, "transport", translate("Transport")) +o:value("tcp", "TCP") +o:value("http", "HTTP") +o:value("ws", "WebSocket") +o:value("httpupgrade", "HTTPUpgrade") +if singbox_tags:find("with_quic") then + o:value("quic", "QUIC") +end +if singbox_tags:find("with_grpc") then + o:value("grpc", "gRPC") +else o:value("grpc", "gRPC-lite") +end +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +if singbox_tags:find("with_wireguard") then + o = s:option(Value, "wireguard_public_key", translate("Public Key")) + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_secret_key", translate("Private Key")) + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key")) + o:depends({ protocol = "wireguard" }) + + o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_mtu", translate("MTU")) + o.default = "1420" + o:depends({ protocol = "wireguard" }) + + o = s:option(Flag, "wireguard_system_interface", translate("System interface")) + o.default = 0 + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_interface_name", translate("System interface name")) + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) + o:depends({ protocol = "wireguard" }) +end + +-- [[ TCP ]]-- +o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) +o:value("none", "none") +o:value("http", "http") +o:depends({ transport = "tcp" }) + +o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) +o:depends({ tcp_guise = "http" }) + +o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) +o.placeholder = "/" +o:depends({ tcp_guise = "http" }) + +-- [[ HTTP ]]-- +o = s:option(DynamicList, "http_host", translate("HTTP Host")) +o:depends({ transport = "http" }) + +o = s:option(Value, "http_path", translate("HTTP Path")) +o.placeholder = "/" +o:depends({ transport = "http" }) + +o = s:option(Flag, "http_h2_health_check", translate("Health check")) +o:depends({ tls = true, transport = "http" }) + +o = s:option(Value, "http_h2_read_idle_timeout", translate("Idle timeout")) +o.default = "15" +o:depends({ http_h2_health_check = true }) + +o = s:option(Value, "http_h2_health_check_timeout", translate("Health check timeout")) +o.default = "15" +o:depends({ http_h2_health_check = true }) + +-- [[ WebSocket ]]-- +o = s:option(Value, "ws_host", translate("WebSocket Host")) +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_path", translate("WebSocket Path")) +o.placeholder = "/" +o:depends({ transport = "ws" }) + +o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data")) +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_maxEarlyData", translate("Early data length")) +o.default = "1024" +o:depends({ ws_enableEarlyData = true }) + +o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) +o:depends({ ws_enableEarlyData = true }) + +-- [[ HTTPUpgrade ]]-- +o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) +o:depends({ transport = "httpupgrade" }) + +o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path")) +o.placeholder = "/" +o:depends({ transport = "httpupgrade" }) + +-- [[ gRPC ]]-- +o = s:option(Value, "grpc_serviceName", "ServiceName") +o:depends({ transport = "grpc" }) + +o = s:option(Flag, "grpc_health_check", translate("Health check")) +o:depends({ transport = "grpc" }) + +o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout")) +o.default = "15" +o:depends({ grpc_health_check = true }) + +o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout")) +o.default = "15" +o:depends({ grpc_health_check = true }) + +o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream")) +o.default = "0" +o:depends({ grpc_health_check = true }) + +-- [[ User-Agent ]]-- +o = s:option(Value, "user_agent", translate("User-Agent")) +o.default = "" +o:value("", translate("default")) +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome") +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox") +o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari") +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge") +o:value("Go-http-client/1.1", "golang") +o:value("curl/7.68.0", "curl") +o:depends({ tcp_guise = "http" }) +o:depends({ transport = "http" }) +o:depends({ transport = "ws" }) +o:depends({ transport = "httpupgrade" }) +o:depends({ protocol = "naive" }) + +-- [[ Mux ]]-- +o = s:option(Flag, "mux", translate("Mux")) +o.rmempty = false +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless", flow = "" }) +o:depends({ protocol = "shadowsocks", uot = "" }) +o:depends({ protocol = "trojan" }) + +o = s:option(ListValue, "mux_type", translate("Mux")) +o:value("smux") +o:value("yamux") +o:value("h2mux") +o:depends({ mux = true }) + +o = s:option(Value, "mux_concurrency", translate("Mux concurrency")) +o.default = 4 +o:depends({ mux = true, tcpbrutal = false }) + +o = s:option(Flag, "mux_padding", translate("Padding")) +o.default = 0 +o:depends({ mux = true }) + +-- [[ TCP Brutal ]]-- +o = s:option(Flag, "tcpbrutal", translate("TCP Brutal")) +o.default = 0 +o:depends({ mux = true }) + +o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps")) +o.default = "10" +o:depends({ tcpbrutal = true }) + +o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps")) +o.default = "50" +o:depends({ tcpbrutal = true }) + +o = s:option(Flag, "shadowtls", "ShadowTLS") +o.default = 0 +o:depends({ protocol = "vmess", tls = false }) +o:depends({ protocol = "shadowsocks", tls = false }) + +o = s:option(ListValue, "shadowtls_version", "ShadowTLS " .. translate("Version")) +o.default = "1" +o:value("1", "ShadowTLS v1") +o:value("2", "ShadowTLS v2") +o:value("3", "ShadowTLS v3") +o:depends({ shadowtls = true }) + +o = s:option(Value, "shadowtls_password", "ShadowTLS " .. translate("Password")) +o.password = true +o:depends({ shadowtls = true, shadowtls_version = "2" }) +o:depends({ shadowtls = true, shadowtls_version = "3" }) + +o = s:option(Value, "shadowtls_serverName", "ShadowTLS " .. translate("Domain")) +o:depends({ shadowtls = true }) + +if singbox_tags:find("with_utls") then + o = s:option(Flag, "shadowtls_utls", "ShadowTLS " .. translate("uTLS")) + o.default = "0" + o:depends({ shadowtls = true }) + + o = s:option(ListValue, "shadowtls_fingerprint", "ShadowTLS " .. translate("Finger Print")) + o:value("chrome") + o:value("firefox") + o:value("edge") + o:value("safari") + -- o:value("360") + o:value("qq") + o:value("ios") + -- o:value("android") + o:value("random") + -- o:value("randomized") + o.default = "chrome" + o:depends({ shadowtls = true, shadowtls_utls = true }) +end + +-- [[ SIP003 plugin ]]-- +o = s:option(Flag, "plugin_enabled", translate("plugin")) +o.default = 0 +o:depends({ protocol = "shadowsocks" }) + +o = s:option(ListValue, "plugin", "SIP003 " .. translate("plugin")) +o.default = "obfs-local" +o:depends({ plugin_enabled = true }) +o:value("obfs-local") +o:value("v2ray-plugin") + +o = s:option(Value, "plugin_opts", translate("opts")) +o:depends({ plugin_enabled = true }) + +o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution.")) +o:value("", translate("Auto")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("https", "HTTPS") + +o = s:option(Value, "domain_resolver_dns", "DNS") +o.datatype = "or(ipaddr,ipaddrport)" +o:value("114.114.114.114") +o:value("223.5.5.5:53") +o.default = o.keylist[1] +o:depends({ domain_resolver = "tcp" }) +o:depends({ domain_resolver = "udp" }) + +o = s:option(Value, "domain_resolver_dns_https", "DNS") +o:value("https://120.53.53.53/dns-query", "DNSPod") +o:value("https://223.5.5.5/dns-query", "AliDNS") +o.default = o.keylist[1] +o:depends({ domain_resolver = "https" }) + +o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect.")) +o.default = "" +o:value("", translate("Auto")) +o:value("prefer_ipv4", translate("Prefer IPv4")) +o:value("prefer_ipv6", translate("Prefer IPv6")) +o:value("ipv4_only", translate("IPv4 Only")) +o:value("ipv6_only", translate("IPv6 Only")) + +local protocols = s.fields["protocol"].keylist +if #protocols > 0 then + for i, v in ipairs(protocols) do + if not v:find("^_") then + local depends_condition = { protocol = v } + if v == "hysteria2" then + depends_condition["hysteria2_realms"] = false + end + s.fields["address"]:depends(depends_condition) + s.fields["port"]:depends(depends_condition) + s.fields["domain_resolver"]:depends(depends_condition) + s.fields["domain_strategy"]:depends(depends_condition) + end + end +end +end +-- [[ Normal single node End ]] + +if not load_shunt_options then + o = s:option(ListValue, "chain_proxy", translate("Chain Proxy")) + o:value("", translate("Close(Not use)")) + if not (load_iface_options or load_urltest_options) then + -- Special node cannot be use pre-proxy. + o:value("1", translate("Preproxy Node")) + o:value("3", translate("Outbound Interface")) + end + o:value("2", translate("Landing Node")) + + o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy.")) + o1:depends({ chain_proxy = "1", hysteria2_realms = false }) + o1.template = m:template_path("/cbi/nodes_listvalue") + o1.group = {} + + o3 = s:option(Value, "outbound_iface", translate("Outbound Interface")) + o3:depends({ chain_proxy = "3" }) + o3:value("", translate("All")) + for _, d in ipairs(netdev_list) do + o3:value(d.name, d.label) + end + + o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy.")) + o2:depends({ chain_proxy = "2", hysteria2_realms = false }) + o2.template = m:template_path("/cbi/nodes_listvalue") + o2.group = {} + + for k1, v1 in pairs(node_list) do + if k1 ~= "shunt_list" and k1 ~= "iface_list" then + for i, v in ipairs(v1) do + if v.id ~= arg[1] then + o1:value(v.id, v.remark) + o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if k1 == "normal_list" then + -- Landing Node not support use special node. + o2:value(v.id, v.remark) + o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + end + end + end +end + +api.luci_types(s1, s) + +if load_shunt_options then + local current_node = m:get(arg[1]) or {} + local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") + setfenv(shunt_lua, getfenv(1))(m, s1, { + node_id = arg[1], + node = current_node, + node_list = node_list, + }) +end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua index d7827092..e0d75ec3 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua @@ -1,901 +1,901 @@ -if not api.finded_com("xray") then - return -end - --- [[ Xray ]] -local m, s1 = ... -local type_name = "Xray" - -s1.fields["type"]:value(type_name, "Xray") -if not s1.fields["type"].default then - s1.fields["type"].default = type_name -end - -if s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "xray_" - -local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol") - -if formvalue_proto then s1.val["protocol"] = formvalue_proto end - -local arg_select_proto = luci.http.formvalue("select_proto") or "" - -local ss_method_list = { - "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" -} - -local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" } - -local header_type_list = { - "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" -} - -local xray_version = api.get_app_version("xray") - -o = s:option(ListValue, "protocol", translate("Protocol")) -o:value("vmess", translate("Vmess")) -o:value("vless", translate("VLESS")) -o:value("http", translate("HTTP")) -o:value("socks", translate("Socks")) -o:value("shadowsocks", translate("Shadowsocks")) -o:value("trojan", translate("Trojan")) -o:value("wireguard", translate("WireGuard")) -if api.compare_versions(xray_version, ">=", "26.1.13") then - o:value("hysteria2", translate("Hysteria2")) -end -if api.compare_versions(xray_version, ">=", "1.8.12") then - o:value("_balancing", translate("Balancing")) -end -o:value("_shunt", translate("Shunt")) -o:value("_iface", translate("Custom Interface")) -function o.custom_cfgvalue(self, section) - if arg_select_proto ~= "" then - return arg_select_proto - else - return m:get(section, self.config_option) - end -end - -local load_balancing_options = s1.val["protocol"] == "_balancing" or arg_select_proto == "_balancing" -local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" -local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" -local load_normal_options = true -if load_balancing_options or load_shunt_options or load_iface_options then - load_normal_options = nil -end -if not arg_select_proto:find("_") then - load_normal_options = true -end - -local netdev_list = api.get_network_devices() -local node_list = api.get_node_list() -local fallback_list = {} -local is_balancer = nil -for k, e in ipairs(node_list.balancing_list or {}) do - if e.id ~= arg[1] then - fallback_list[#fallback_list + 1] = { - id = e["id"], - remark = e["remark"], - group = e["group"], - fallback = e.o["fallback_node"], - } - else - is_balancer = true - end -end - -if load_balancing_options then -- [[ Load balancing Start ]] - o = s:option(ListValue, "node_add_mode", translate("Node Addition Method")) - o:depends({ protocol = "_balancing" }) - o.default = "manual" - o:value("manual", translate("Manual")) - o:value("batch", translate("Batch")) - - o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document")) - o:depends({ node_add_mode = "manual" }) - o.widget = "checkbox" - o.template = m:template_path("/cbi/nodes_multivalue") - o.group = {} - for k1, v1 in pairs(node_list) do - if k1 == "socks_list" or k1 == "normal_list" then - for i, v in ipairs(v1) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" - end - end - end - -- Reading the old DynamicList - function o.custom_cfgvalue(self, section) - return table.concat(m:get(section, "balancing_node") or {}, " ") - end - -- Write-and-hold DynamicList - function o.custom_write(self, section, value) - local old = m:get(section, "balancing_node") or {} - local new, set = {}, {} - for v in value:gmatch("%S+") do - new[#new + 1] = v - set[v] = 1 - end - for _, v in ipairs(old) do - if not set[v] then - m:set(section, "balancing_node", new) - return - end - set[v] = nil - end - for _ in pairs(set) do - m:set(section, "balancing_node", new) - return - end - end - - o = s:option(MultiValue, "node_group", translate("Select Group")) - o:depends({ node_add_mode = "batch" }) - o.widget = "checkbox" - o:value("default", translate("default")) - for k, v in pairs(groups) do - o:value(api.UrlEncode(k), k) - end - - o = s:option(Value, "node_match_rule", translate("Node Matching Rules")) - o:depends({ node_add_mode = "batch" }) - local descrStr = "Example: ^A && B && !C && D$
" - descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" - descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
" - descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
" - descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)." - o.description = translate(descrStr) - - o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy")) - o:depends({ protocol = "_balancing" }) - o:value("random") - o:value("roundRobin") - o:value("leastPing") - o:value("leastLoad") - o.default = "random" - - -- Fallback Node - o = s:option(ListValue, "fallback_node", translate("Fallback Node")) - o.group = {"",""} - o:value("", translate("Close(Not use)")) - o:value("_direct", translate("Direct Connection")) - o:depends({ protocol = "_balancing" }) - o.template = m:template_path("/cbi/nodes_listvalue") - -- Maximum number of fallback nesting layers - local MAX_FALLBACK_DEPTH = 3 - -- Check if a loop will form. - local function will_loop(start_id, target_id, depth) - depth = depth or 0 - -- Recursion stops after the maximum depth is exceeded. - if depth >= MAX_FALLBACK_DEPTH then - return false - end - for _, v in ipairs(fallback_list) do - if v.id == target_id then - local fb = v.fallback - -- No fallback - if not fb or fb == "" or fb == "_direct" then - return false - end - -- Loopback detected - if fb == start_id then - return true - end - -- Continue recursive checking - return will_loop(start_id, fb, depth + 1) - end - end - return false - end - -- Get fallback chain depth - local function get_fallback_depth(id, depth) - depth = depth or 0 - if depth >= MAX_FALLBACK_DEPTH then - return depth - end - for _, v in ipairs(fallback_list) do - if v.id == id then - local fb = v.fallback - if not fb or fb == "" or fb == "_direct" then - return depth - end - return get_fallback_depth(fb, depth + 1) - end - end - return depth - end - for _, v in ipairs(fallback_list) do - local depth = get_fallback_depth(v.id) - -- Once the maximum number of nested doll layers is exceeded, further selection of the balancer is not allowed. - if depth < MAX_FALLBACK_DEPTH - and not will_loop(arg[1], v.id) - then - o:value(v.id, v.remark) - o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default") - end - end - for k1, v1 in pairs(node_list) do - if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then - for i, v in ipairs(v1) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - end - end - - o = s:option(Flag, "useCustomProbeUrl", translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) - o:depends({ protocol = "_balancing" }) - - o = s:option(Value, "probeUrl", translate("Probe URL")) - o:depends({ useCustomProbeUrl = true }) - o:value("https://cp.cloudflare.com/", "Cloudflare") - o:value("https://www.gstatic.com/generate_204", "Gstatic") - o:value("https://www.google.com/generate_204", "Google") - o:value("https://www.youtube.com/generate_204", "YouTube") - o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") - o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") - o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") - o.default = o.keylist[3] - o.description = translate("The URL used to detect the connection status.") - - o = s:option(Value, "probeInterval", translate("Probe Interval")) - o:depends({ protocol = "_balancing" }) - o.default = "1m" - o.placeholder = "1m" - o.description = translate("The interval between initiating probes.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") - - o = s:option(Value, "expected", translate("Preferred Node Count")) - o:depends({ balancingStrategy = "leastLoad" }) - o.datatype = "uinteger" - o.default = "2" - o.placeholder = "2" - o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.") - - o = s:option(Value, "tolerance", translate("Failure Tolerance (%)")) - o:depends({ balancingStrategy = "leastLoad" }) - o.datatype = "uinteger" - o.default = "10" - o.placeholder = "10" - o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.") -end -- [[ Load balancing End ]] - -if load_iface_options then -- [[ Custom Interface Start ]] - o = s:option(Value, "iface", translate("Interface")) - o:depends({ protocol = "_iface" }) - for _, d in ipairs(netdev_list) do - o:value(d.name, d.label) - end -end -- [[ Custom Interface End ]] - - --- [[ Normal single node Start ]] -if load_normal_options then - -o = s:option(Value, "address", translate("Address (Support Domain Name)")) - -o = s:option(Value, "port", translate("Port")) -o.datatype = "port" - -o = s:option(Value, "uuid", translate("ID")) -o.password = true -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) - -o = s:option(Value, "username", translate("Username")) -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) - -o = s:option(Value, "password", translate("Password")) -o.password = true -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - -o = s:option(ListValue, "security", translate("Encrypt Method")) -for a, t in ipairs(security_list) do o:value(t) end -o:depends({ protocol = "vmess" }) - -o = s:option(Value, "encryption", translate("Encrypt Method") .. " (encryption)") -o.default = "none" -o.placeholder = "none" -o:depends({ protocol = "vless" }) -o.validate = function(self, value) - value = api.trim(value) - return (value == "" and "none" or value) -end - -o = s:option(ListValue, "ss_method", translate("Encrypt Method")) -for a, t in ipairs(ss_method_list) do o:value(t) end -o:depends({ protocol = "shadowsocks" }) - -o = s:option(ListValue, "flow", translate("flow")) -o.default = "" -o:value("", translate("Disable")) -o:value("xtls-rprx-vision") -o:value("xtls-rprx-vision-udp443") -o:depends({ protocol = "vless" }) - ----- [[hysteria2]] -o = s:option(Value, "hysteria2_hop", translate("Port hopping range")) -o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") -o:depends({ protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(Value, "hysteria2_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({ protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(Flag, "hysteria2_realms", translate("Realms")) -o.default = "0" -if api.compare_versions(xray_version, ">", "26.5.9") then - o:depends({ protocol = "hysteria2"}) -else - o:depends({ protocol = "__hide"}) -end - -o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") -o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) -o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } -o:depends({ hysteria2_realms = "1" }) - -o = s:option(Value, "hysteria2_auth_password", translate("Auth Password")) -o.password = true -o:depends({ protocol = "hysteria2"}) - -o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) -o:value("", translate("Disable")) -o:value("salamander") -o:value("gecko") -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) -o:depends({ hysteria2_obfs_type = "salamander" }) -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) -o.datatype = "uinteger" -o.placeholder = "512" -o.default = "512" -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) -o.datatype = "uinteger" -o.placeholder = "1200" -o.default = "1200" -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") -o.datatype = "range(4,120)" -o:depends({ protocol = "hysteria2"}) - -o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") -o.datatype = "range(2,60)" -o:depends({ protocol = "hysteria2"}) - -o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection")) -o.default = "0" -o:depends({ protocol = "hysteria2"}) ----- [[hysteria2 end]] - -o = s:option(Flag, "tls", translate("TLS")) -o.default = 0 -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "shadowsocks" }) - -o = s:option(Flag, "reality", translate("REALITY")) -o.default = 0 -o:depends({ tls = true, transport = "raw" }) -o:depends({ tls = true, transport = "ws" }) -o:depends({ tls = true, transport = "grpc" }) -o:depends({ tls = true, transport = "httpupgrade" }) -o:depends({ tls = true, transport = "xhttp" }) - -o = s:option(ListValue, "alpn", translate("alpn")) -o.default = "default" -o:value("default", translate("Default")) -o:value("h3") -o:value("h2") -o:value("h3,h2") -o:value("http/1.1") -o:value("h2,http/1.1") -o:value("h3,h2,http/1.1") -o:depends({ tls = true, reality = false }) - --- o = s:option(Value, "minversion", translate("minversion")) --- o.default = "1.3" --- o:value("1.3") --- o:depends({ tls = true }) - -o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain")) -o:depends({ tls = true }) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)")) -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2" }) -o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") .. - string.format("%s", "→ " .. translate("Fetch Manually")) - -o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name.")) -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2" }) - -o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)")) -o.default = "0" -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2" }) - -o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format.")) -o.default = "" -o.rows = 5 -o.wrap = "off" -o:depends({ tls_certificate = true }) -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") - return value -end - -o = s:option(Flag, "ech", translate("ECH")) -o.default = "0" -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(TextValue, "ech_config", translate("ECH Config")) -o.default = "" -o.rows = 5 -o.wrap = "soft" -o:depends({ ech = true }) -o.validate = function(self, value) - return api.trim(value:gsub("[\r\n]", "")) -end - --- [[ REALITY ]] -- -o = s:option(Value, "reality_publicKey", translate("Public Key")) -o:depends({ tls = true, reality = true }) - -o = s:option(Value, "reality_shortId", translate("Short Id")) -o:depends({ tls = true, reality = true }) - -o = s:option(Value, "reality_spiderX", translate("Spider X")) -o.placeholder = "/" -o:depends({ tls = true, reality = true }) - -o = s:option(Flag, "utls", translate("uTLS")) -o.default = "0" -o:depends({ tls = true, reality = false }) - -o = s:option(ListValue, "fingerprint", translate("Finger Print")) -o:value("chrome") -o:value("firefox") -o:value("edge") -o:value("safari") -o:value("360") -o:value("qq") -o:value("ios") -o:value("android") -o:value("random") -o:value("randomized") -o:value("unsafe") -o.default = "chrome" -o:depends({ tls = true, utls = true }) -o:depends({ tls = true, reality = true }) - -o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65")) -o.default = "0" -o:depends({ tls = true, reality = true }) - -o = s:option(DynamicList, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites.")) -o:value("TLS_AES_128_GCM_SHA256") -o:value("TLS_AES_256_GCM_SHA384") -o:value("TLS_CHACHA20_POLY1305_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") -o:value("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") -o:value("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") -o:value("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") -o:value("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") -o:value("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") -o:value("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") -o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256") -o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") -o:depends({ tls = true, reality = false }) - -o = s:option(TextValue, "reality_mldsa65Verify", "ML-DSA-65 " .. translate("Public key")) -o.default = "" -o.rows = 5 -o.wrap = "soft" -o:depends({ use_mldsa65Verify = true }) -o.validate = function(self, value) - return api.trim(value:gsub("[\r\n]", "")) -end - -o = s:option(ListValue, "transport", translate("Transport")) -o:value("raw", "RAW (TCP)") -o:value("mkcp", "mKCP") -o:value("ws", "WebSocket") -o:value("grpc", "gRPC") -o:value("httpupgrade", "HttpUpgrade") -o:value("xhttp", "XHTTP") -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - -o = s:option(Value, "wireguard_public_key", translate("Public Key")) -o:depends({ protocol = "wireguard" }) - -o = s:option(Value, "wireguard_secret_key", translate("Private Key")) -o:depends({ protocol = "wireguard" }) - -o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key")) -o:depends({ protocol = "wireguard" }) - -o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) -o:depends({ protocol = "wireguard" }) - -o = s:option(Value, "wireguard_mtu", translate("MTU")) -o.default = "1420" -o:depends({ protocol = "wireguard" }) - -if api.compare_versions(xray_version, ">=", "1.8.0") then - o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) - o:depends({ protocol = "wireguard" }) -end - -o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive")) -o.default = "0" -o:depends({ protocol = "wireguard" }) - --- [[ RAW ]]-- -o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) -o:value("none", "none") -o:value("http", "http") -o:depends({ transport = "raw" }) - -o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) -o:depends({ tcp_guise = "http" }) - -o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) -o.placeholder = "/" -o:depends({ tcp_guise = "http" }) - --- [[ mKCP ]]-- -o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) -for a, t in ipairs(header_type_list) do o:value(t) end -o:depends({ transport = "mkcp" }) - -o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) -o:depends({ mkcp_guise = "dns" }) - -o = s:option(Value, "mkcp_mtu", translate("KCP MTU")) -o.datatype = "uinteger" -o.default = 1350 -o:depends({ transport = "mkcp" }) - -o = s:option(Value, "mkcp_seed", translate("KCP Seed")) -o:depends({ transport = "mkcp" }) - --- [[ WebSocket ]]-- -o = s:option(Value, "ws_host", translate("WebSocket Host")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_path", translate("WebSocket Path")) -o.placeholder = "/" -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)")) -o.datatype = "integer" -o:depends({ transport = "ws" }) - --- [[ gRPC ]]-- -o = s:option(Value, "grpc_serviceName", "ServiceName") -o:depends({ transport = "grpc" }) - -o = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode")) -o:value("gun") -o:value("multi") -o:depends({ transport = "grpc" }) - -o = s:option(Flag, "grpc_health_check", translate("Health check")) -o:depends({ transport = "grpc" }) - -o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout")) -o.default = "10" -o:depends({ grpc_health_check = true }) - -o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout")) -o.default = "20" -o:depends({ grpc_health_check = true }) - -o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream")) -o.default = "0" -o:depends({ grpc_health_check = true }) - -o = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size")) -o.default = "0" -o:depends({ transport = "grpc" }) - --- [[ HttpUpgrade ]]-- -o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host")) -o:depends({ transport = "httpupgrade" }) - -o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path")) -o.placeholder = "/" -o:depends({ transport = "httpupgrade" }) - --- [[ XHTTP ]]-- -o = s:option(ListValue, "xhttp_mode", "XHTTP " .. translate("Mode")) -o:depends({ transport = "xhttp" }) -o.default = "auto" -o:value("auto") -o:value("packet-up") -o:value("stream-up") -o:value("stream-one") - -o = s:option(Value, "xhttp_host", translate("XHTTP Host")) -o:depends({ transport = "xhttp" }) - -o = s:option(Value, "xhttp_path", translate("XHTTP Path")) -o.placeholder = "/" -o:depends({ transport = "xhttp" }) - -o = s:option(Flag, "use_xhttp_extra", translate("XHTTP Extra")) -o.default = "0" -o:depends({ transport = "xhttp" }) - -o = s:option(TextValue, "xhttp_extra", " ", translate("An XHttpObject in JSON format, used for sharing.")) -o:depends({ use_xhttp_extra = true }) -o.rows = 10 -o.wrap = "off" -o.datatype = "json" -local o_validate = o.validate -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n") - local v = o_validate(self, value) - if v then return v end - return nil, "XHTTP Extra " .. translate("Must be JSON text!") -end -o.custom_cfgvalue = function(self, section, value) - local raw = m:get(section, "xhttp_extra") - if raw then - return api.base64Decode(raw) - end -end -o.custom_write = function(self, section, value) - m:set(section, "xhttp_extra", api.base64Encode(value) or "") - local success, data = pcall(api.jsonc.parse, value) - if success and data then - local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address) - or (data.downloadSettings and data.downloadSettings.address) - if address and address ~= "" then - address = address:gsub("^%[", ""):gsub("%]$", "") - m:set(section, "download_address", address) - else - m:del(section, "download_address") - end - else - m:del(section, "download_address") - end -end -o.custom_remove = function(self, section, value) - m:del(section, "xhttp_extra") - m:del(section, "download_address") -end - --- [[ User-Agent ]]-- -o = s:option(Value, "user_agent", translate("User-Agent")) -o.default = "" -o:value("", translate("default")) -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome") -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox") -o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari") -o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge") -o:value("Go-http-client/1.1", "golang") -o:value("curl/7.68.0", "curl") -o:depends({ tcp_guise = "http" }) -o:depends({ transport = "ws" }) -o:depends({ transport = "httpupgrade" }) -o:depends({ transport = "xhttp" }) -o:depends({ transport = "grpc" }) - --- [[ Mux.Cool ]]-- -o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool")) -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless", transport = "raw" }) -o:depends({ protocol = "vless", transport = "ws" }) -o:depends({ protocol = "vless", transport = "grpc" }) -o:depends({ protocol = "vless", transport = "httpupgrade" }) -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - -o = s:option(Value, "mux_concurrency", translate("Mux concurrency")) -o.default = -1 -o:depends({ mux = true }) - -o = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency")) -o.default = 8 -o:depends({ mux = true }) - ---[[FinalMask]] -o = s:option(Flag, "use_finalmask", "FinalMask") -o.default = "0" -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "wireguard" }) -o:depends({ protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(TextValue, "finalmask", " ") -o:depends({ use_finalmask = true }) -o.rows = 10 -o.wrap = "off" -o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" .. - translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.") -o.datatype = "json" -local o_validate = o.validate -o.validate = function(self, value) - value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n") - local v = o_validate(self, value) - if v then return v end - return nil, "FinalMask " .. translate("Must be JSON text!") -end -o.custom_cfgvalue = function(self, section, value) - local raw = m:get(section, "finalmask") - if raw then - return api.base64Decode(raw) - end -end -o.custom_write = function(self, section, value) - m:set(section, "finalmask", api.base64Encode(value) or "") -end - ---[[Fast Open]] -o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) -o.default = 0 - ---[[tcpMptcp]] -o = s:option(Flag, "tcpMptcp", "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration.")) -o.default = 0 - -o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution.")) -o:value("", translate("Auto")) -o:value("tcp", "TCP") -o:value("udp", "UDP") -o:value("https", "HTTPS") - -o = s:option(Value, "domain_resolver_dns", "DNS") -o.datatype = "or(ipaddr,ipaddrport)" -o:value("114.114.114.114") -o:value("223.5.5.5:53") -o.default = o.keylist[1] -o:depends({ domain_resolver = "tcp" }) -o:depends({ domain_resolver = "udp" }) - -o = s:option(Value, "domain_resolver_dns_https", "DNS") -o:value("https://120.53.53.53/dns-query", "DNSPod") -o:value("https://223.5.5.5/dns-query", "AliDNS") -o.default = o.keylist[1] -o:depends({ domain_resolver = "https" }) - -o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect.")) -o.default = "" -o:value("", translate("Auto")) -o:value("UseIPv4", translate("IPv4 Only")) -o:value("UseIPv6", translate("IPv6 Only")) - -o = s:option(Flag, "happy_eyeballs", translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection.")) -o.default = 0 - -local protocols = s.fields["protocol"].keylist -if #protocols > 0 then - for i, v in ipairs(protocols) do - if not v:find("^_") then - local depends_condition = { protocol = v } - if v == "hysteria2" then - depends_condition["hysteria2_realms"] = false - end - s.fields["address"]:depends(depends_condition) - s.fields["port"]:depends(depends_condition) - s.fields["domain_resolver"]:depends(depends_condition) - s.fields["happy_eyeballs"]:depends(depends_condition) - - local strategy_depends = api.clone(depends_condition) - strategy_depends["happy_eyeballs"] = false - s.fields["domain_strategy"]:depends(strategy_depends) - - if v ~= "hysteria2" then - s.fields["tcp_fast_open"]:depends({ protocol = v }) - s.fields["tcpMptcp"]:depends({ protocol = v }) - end - end - end -end -end --- [[ Normal single node End ]] - -if not load_shunt_options then - o = s:option(ListValue, "chain_proxy", translate("Chain Proxy")) - o:value("", translate("Close(Not use)")) - if not (load_iface_options or load_balancing_options) then - -- Special node cannot be use pre-proxy. - o:value("1", translate("Preproxy Node")) - o:value("3", translate("Outbound Interface")) - end - o:value("2", translate("Landing Node")) - - o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy.")) - o1:depends({ chain_proxy = "1", hysteria2_realms = false }) - o1.template = m:template_path("/cbi/nodes_listvalue") - o1.group = {} - - o3 = s:option(Value, "outbound_iface", translate("Outbound Interface")) - o3:depends({ chain_proxy = "3" }) - o3:value("", translate("All")) - for _, d in ipairs(netdev_list) do - o3:value(d.name, d.label) - end - - o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy.")) - o2:depends({ chain_proxy = "2", hysteria2_realms = false }) - o2.template = m:template_path("/cbi/nodes_listvalue") - o2.group = {} - - for k1, v1 in pairs(node_list) do - if k1 ~= "shunt_list" and k1 ~= "iface_list" then - for i, v in ipairs(v1) do - if v.id ~= arg[1] then - o1:value(v.id, v.remark) - o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if k1 == "normal_list" then - -- Landing Node not support use special node. - o2:value(v.id, v.remark) - o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - end - end - end - end -end - -api.luci_types(s1, s) - -if load_shunt_options then - local current_node = m:get(arg[1]) or {} - local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") - setfenv(shunt_lua, getfenv(1))(m, s1, { - node_id = arg[1], - node = current_node, - node_list = node_list, - }) -end +if not api.finded_com("xray") then + return +end + +-- [[ Xray ]] +local m, s1 = ... +local type_name = "Xray" + +s1.fields["type"]:value(type_name, "Xray") +if not s1.fields["type"].default then + s1.fields["type"].default = type_name +end + +if s1.val["type"] ~= type_name then + return +end + +local s = NamedSection(m, arg[1], "server") +s.type_name = type_name +s.option_prefix = "xray_" + +local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol") + +if formvalue_proto then s1.val["protocol"] = formvalue_proto end + +local arg_select_proto = luci.http.formvalue("select_proto") or "" + +local ss_method_list = { + "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" } + +local header_type_list = { + "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" +} + +local xray_version = api.get_app_version("xray") + +o = s:option(ListValue, "protocol", translate("Protocol")) +o:value("vmess", translate("Vmess")) +o:value("vless", translate("VLESS")) +o:value("http", translate("HTTP")) +o:value("socks", translate("Socks")) +o:value("shadowsocks", translate("Shadowsocks")) +o:value("trojan", translate("Trojan")) +o:value("wireguard", translate("WireGuard")) +if api.compare_versions(xray_version, ">=", "26.1.13") then + o:value("hysteria2", translate("Hysteria2")) +end +if api.compare_versions(xray_version, ">=", "1.8.12") then + o:value("_balancing", translate("Balancing")) +end +o:value("_shunt", translate("Shunt")) +o:value("_iface", translate("Custom Interface")) +function o.custom_cfgvalue(self, section) + if arg_select_proto ~= "" then + return arg_select_proto + else + return m:get(section, self.config_option) + end +end + +local load_balancing_options = s1.val["protocol"] == "_balancing" or arg_select_proto == "_balancing" +local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt" +local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface" +local load_normal_options = true +if load_balancing_options or load_shunt_options or load_iface_options then + load_normal_options = nil +end +if not arg_select_proto:find("_") then + load_normal_options = true +end + +local netdev_list = api.get_network_devices() +local node_list = api.get_node_list() +local fallback_list = {} +local is_balancer = nil +for k, e in ipairs(node_list.balancing_list or {}) do + if e.id ~= arg[1] then + fallback_list[#fallback_list + 1] = { + id = e["id"], + remark = e["remark"], + group = e["group"], + fallback = e.o["fallback_node"], + } + else + is_balancer = true + end +end + +if load_balancing_options then -- [[ Load balancing Start ]] + o = s:option(ListValue, "node_add_mode", translate("Node Addition Method")) + o:depends({ protocol = "_balancing" }) + o.default = "manual" + o:value("manual", translate("Manual")) + o:value("batch", translate("Batch")) + + o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document")) + o:depends({ node_add_mode = "manual" }) + o.widget = "checkbox" + o.template = m:template_path("/cbi/nodes_multivalue") + o.group = {} + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + end + end + -- Reading the old DynamicList + function o.custom_cfgvalue(self, section) + return table.concat(m:get(section, "balancing_node") or {}, " ") + end + -- Write-and-hold DynamicList + function o.custom_write(self, section, value) + local old = m:get(section, "balancing_node") or {} + local new, set = {}, {} + for v in value:gmatch("%S+") do + new[#new + 1] = v + set[v] = 1 + end + for _, v in ipairs(old) do + if not set[v] then + m:set(section, "balancing_node", new) + return + end + set[v] = nil + end + for _ in pairs(set) do + m:set(section, "balancing_node", new) + return + end + end + + o = s:option(MultiValue, "node_group", translate("Select Group")) + o:depends({ node_add_mode = "batch" }) + o.widget = "checkbox" + o:value("default", translate("default")) + for k, v in pairs(groups) do + o:value(api.UrlEncode(k), k) + end + + o = s:option(Value, "node_match_rule", translate("Node Matching Rules")) + o:depends({ node_add_mode = "batch" }) + local descrStr = "Example: ^A && B && !C && D$
" + descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
" + descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
" + descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
" + descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)." + o.description = translate(descrStr) + + o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy")) + o:depends({ protocol = "_balancing" }) + o:value("random") + o:value("roundRobin") + o:value("leastPing") + o:value("leastLoad") + o.default = "random" + + -- Fallback Node + o = s:option(ListValue, "fallback_node", translate("Fallback Node")) + o.group = {"",""} + o:value("", translate("Close(Not use)")) + o:value("_direct", translate("Direct Connection")) + o:depends({ protocol = "_balancing" }) + o.template = m:template_path("/cbi/nodes_listvalue") + -- Maximum number of fallback nesting layers + local MAX_FALLBACK_DEPTH = 3 + -- Check if a loop will form. + local function will_loop(start_id, target_id, depth) + depth = depth or 0 + -- Recursion stops after the maximum depth is exceeded. + if depth >= MAX_FALLBACK_DEPTH then + return false + end + for _, v in ipairs(fallback_list) do + if v.id == target_id then + local fb = v.fallback + -- No fallback + if not fb or fb == "" or fb == "_direct" then + return false + end + -- Loopback detected + if fb == start_id then + return true + end + -- Continue recursive checking + return will_loop(start_id, fb, depth + 1) + end + end + return false + end + -- Get fallback chain depth + local function get_fallback_depth(id, depth) + depth = depth or 0 + if depth >= MAX_FALLBACK_DEPTH then + return depth + end + for _, v in ipairs(fallback_list) do + if v.id == id then + local fb = v.fallback + if not fb or fb == "" or fb == "_direct" then + return depth + end + return get_fallback_depth(fb, depth + 1) + end + end + return depth + end + for _, v in ipairs(fallback_list) do + local depth = get_fallback_depth(v.id) + -- Once the maximum number of nested doll layers is exceeded, further selection of the balancer is not allowed. + if depth < MAX_FALLBACK_DEPTH + and not will_loop(arg[1], v.id) + then + o:value(v.id, v.remark) + o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + for k1, v1 in pairs(node_list) do + if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then + for i, v in ipairs(v1) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + end + + o = s:option(Flag, "useCustomProbeUrl", translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) + o:depends({ protocol = "_balancing" }) + + o = s:option(Value, "probeUrl", translate("Probe URL")) + o:depends({ useCustomProbeUrl = true }) + o:value("https://cp.cloudflare.com/", "Cloudflare") + o:value("https://www.gstatic.com/generate_204", "Gstatic") + o:value("https://www.google.com/generate_204", "Google") + o:value("https://www.youtube.com/generate_204", "YouTube") + o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") + o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") + o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)") + o.default = o.keylist[3] + o.description = translate("The URL used to detect the connection status.") + + o = s:option(Value, "probeInterval", translate("Probe Interval")) + o:depends({ protocol = "_balancing" }) + o.default = "1m" + o.placeholder = "1m" + o.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") + + o = s:option(Value, "expected", translate("Preferred Node Count")) + o:depends({ balancingStrategy = "leastLoad" }) + o.datatype = "uinteger" + o.default = "2" + o.placeholder = "2" + o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.") + + o = s:option(Value, "tolerance", translate("Failure Tolerance (%)")) + o:depends({ balancingStrategy = "leastLoad" }) + o.datatype = "uinteger" + o.default = "10" + o.placeholder = "10" + o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.") +end -- [[ Load balancing End ]] + +if load_iface_options then -- [[ Custom Interface Start ]] + o = s:option(Value, "iface", translate("Interface")) + o:depends({ protocol = "_iface" }) + for _, d in ipairs(netdev_list) do + o:value(d.name, d.label) + end +end -- [[ Custom Interface End ]] + + +-- [[ Normal single node Start ]] +if load_normal_options then + +o = s:option(Value, "address", translate("Address (Support Domain Name)")) + +o = s:option(Value, "port", translate("Port")) +o.datatype = "port" + +o = s:option(Value, "uuid", translate("ID")) +o.password = true +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) + +o = s:option(Value, "username", translate("Username")) +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) + +o = s:option(Value, "password", translate("Password")) +o.password = true +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +o = s:option(ListValue, "security", translate("Encrypt Method")) +for a, t in ipairs(security_list) do o:value(t) end +o:depends({ protocol = "vmess" }) + +o = s:option(Value, "encryption", translate("Encrypt Method") .. " (encryption)") +o.default = "none" +o.placeholder = "none" +o:depends({ protocol = "vless" }) +o.validate = function(self, value) + value = api.trim(value) + return (value == "" and "none" or value) +end + +o = s:option(ListValue, "ss_method", translate("Encrypt Method")) +for a, t in ipairs(ss_method_list) do o:value(t) end +o:depends({ protocol = "shadowsocks" }) + +o = s:option(ListValue, "flow", translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:value("xtls-rprx-vision-udp443") +o:depends({ protocol = "vless" }) + +---- [[hysteria2]] +o = s:option(Value, "hysteria2_hop", translate("Port hopping range")) +o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") +o:depends({ protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(Value, "hysteria2_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({ protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(Flag, "hysteria2_realms", translate("Realms")) +o.default = "0" +if api.compare_versions(xray_version, ">", "26.5.9") then + o:depends({ protocol = "hysteria2"}) +else + o:depends({ protocol = "__hide"}) +end + +o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") +o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) +o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } +o:depends({ hysteria2_realms = "1" }) + +o = s:option(Value, "hysteria2_auth_password", translate("Auth Password")) +o.password = true +o:depends({ protocol = "hysteria2"}) + +o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) +o:value("", translate("Disable")) +o:value("salamander") +o:value("gecko") +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) +o:depends({ hysteria2_obfs_type = "salamander" }) +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) +o.datatype = "uinteger" +o.placeholder = "512" +o.default = "512" +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) +o.datatype = "uinteger" +o.placeholder = "1200" +o.default = "1200" +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") +o.datatype = "range(4,120)" +o:depends({ protocol = "hysteria2"}) + +o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)") +o.datatype = "range(2,60)" +o:depends({ protocol = "hysteria2"}) + +o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection")) +o.default = "0" +o:depends({ protocol = "hysteria2"}) +---- [[hysteria2 end]] + +o = s:option(Flag, "tls", translate("TLS")) +o.default = 0 +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "shadowsocks" }) + +o = s:option(Flag, "reality", translate("REALITY")) +o.default = 0 +o:depends({ tls = true, transport = "raw" }) +o:depends({ tls = true, transport = "ws" }) +o:depends({ tls = true, transport = "grpc" }) +o:depends({ tls = true, transport = "httpupgrade" }) +o:depends({ tls = true, transport = "xhttp" }) + +o = s:option(ListValue, "alpn", translate("alpn")) +o.default = "default" +o:value("default", translate("Default")) +o:value("h3") +o:value("h2") +o:value("h3,h2") +o:value("http/1.1") +o:value("h2,http/1.1") +o:value("h3,h2,http/1.1") +o:depends({ tls = true, reality = false }) + +-- o = s:option(Value, "minversion", translate("minversion")) +-- o.default = "1.3" +-- o:value("1.3") +-- o:depends({ tls = true }) + +o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain")) +o:depends({ tls = true }) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)")) +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2" }) +o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") .. + string.format("%s", "→ " .. translate("Fetch Manually")) + +o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name.")) +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2" }) + +o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)")) +o.default = "0" +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2" }) + +o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format.")) +o.default = "" +o.rows = 5 +o.wrap = "off" +o:depends({ tls_certificate = true }) +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n") + return value +end + +o = s:option(Flag, "ech", translate("ECH")) +o.default = "0" +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(TextValue, "ech_config", translate("ECH Config")) +o.default = "" +o.rows = 5 +o.wrap = "soft" +o:depends({ ech = true }) +o.validate = function(self, value) + return api.trim(value:gsub("[\r\n]", "")) +end + +-- [[ REALITY ]] -- +o = s:option(Value, "reality_publicKey", translate("Public Key")) +o:depends({ tls = true, reality = true }) + +o = s:option(Value, "reality_shortId", translate("Short Id")) +o:depends({ tls = true, reality = true }) + +o = s:option(Value, "reality_spiderX", translate("Spider X")) +o.placeholder = "/" +o:depends({ tls = true, reality = true }) + +o = s:option(Flag, "utls", translate("uTLS")) +o.default = "0" +o:depends({ tls = true, reality = false }) + +o = s:option(ListValue, "fingerprint", translate("Finger Print")) +o:value("chrome") +o:value("firefox") +o:value("edge") +o:value("safari") +o:value("360") +o:value("qq") +o:value("ios") +o:value("android") +o:value("random") +o:value("randomized") +o:value("unsafe") +o.default = "chrome" +o:depends({ tls = true, utls = true }) +o:depends({ tls = true, reality = true }) + +o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65")) +o.default = "0" +o:depends({ tls = true, reality = true }) + +o = s:option(DynamicList, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites.")) +o:value("TLS_AES_128_GCM_SHA256") +o:value("TLS_AES_256_GCM_SHA384") +o:value("TLS_CHACHA20_POLY1305_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") +o:value("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") +o:value("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") +o:value("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") +o:value("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") +o:value("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") +o:value("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") +o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256") +o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") +o:depends({ tls = true, reality = false }) + +o = s:option(TextValue, "reality_mldsa65Verify", "ML-DSA-65 " .. translate("Public key")) +o.default = "" +o.rows = 5 +o.wrap = "soft" +o:depends({ use_mldsa65Verify = true }) +o.validate = function(self, value) + return api.trim(value:gsub("[\r\n]", "")) +end + +o = s:option(ListValue, "transport", translate("Transport")) +o:value("raw", "RAW (TCP)") +o:value("mkcp", "mKCP") +o:value("ws", "WebSocket") +o:value("grpc", "gRPC") +o:value("httpupgrade", "HttpUpgrade") +o:value("xhttp", "XHTTP") +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +o = s:option(Value, "wireguard_public_key", translate("Public Key")) +o:depends({ protocol = "wireguard" }) + +o = s:option(Value, "wireguard_secret_key", translate("Private Key")) +o:depends({ protocol = "wireguard" }) + +o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key")) +o:depends({ protocol = "wireguard" }) + +o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) +o:depends({ protocol = "wireguard" }) + +o = s:option(Value, "wireguard_mtu", translate("MTU")) +o.default = "1420" +o:depends({ protocol = "wireguard" }) + +if api.compare_versions(xray_version, ">=", "1.8.0") then + o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) + o:depends({ protocol = "wireguard" }) +end + +o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive")) +o.default = "0" +o:depends({ protocol = "wireguard" }) + +-- [[ RAW ]]-- +o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) +o:value("none", "none") +o:value("http", "http") +o:depends({ transport = "raw" }) + +o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) +o:depends({ tcp_guise = "http" }) + +o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) +o.placeholder = "/" +o:depends({ tcp_guise = "http" }) + +-- [[ mKCP ]]-- +o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) +for a, t in ipairs(header_type_list) do o:value(t) end +o:depends({ transport = "mkcp" }) + +o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) +o:depends({ mkcp_guise = "dns" }) + +o = s:option(Value, "mkcp_mtu", translate("KCP MTU")) +o.datatype = "uinteger" +o.default = 1350 +o:depends({ transport = "mkcp" }) + +o = s:option(Value, "mkcp_seed", translate("KCP Seed")) +o:depends({ transport = "mkcp" }) + +-- [[ WebSocket ]]-- +o = s:option(Value, "ws_host", translate("WebSocket Host")) +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_path", translate("WebSocket Path")) +o.placeholder = "/" +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)")) +o.datatype = "integer" +o:depends({ transport = "ws" }) + +-- [[ gRPC ]]-- +o = s:option(Value, "grpc_serviceName", "ServiceName") +o:depends({ transport = "grpc" }) + +o = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode")) +o:value("gun") +o:value("multi") +o:depends({ transport = "grpc" }) + +o = s:option(Flag, "grpc_health_check", translate("Health check")) +o:depends({ transport = "grpc" }) + +o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout")) +o.default = "10" +o:depends({ grpc_health_check = true }) + +o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout")) +o.default = "20" +o:depends({ grpc_health_check = true }) + +o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream")) +o.default = "0" +o:depends({ grpc_health_check = true }) + +o = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size")) +o.default = "0" +o:depends({ transport = "grpc" }) + +-- [[ HttpUpgrade ]]-- +o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host")) +o:depends({ transport = "httpupgrade" }) + +o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path")) +o.placeholder = "/" +o:depends({ transport = "httpupgrade" }) + +-- [[ XHTTP ]]-- +o = s:option(ListValue, "xhttp_mode", "XHTTP " .. translate("Mode")) +o:depends({ transport = "xhttp" }) +o.default = "auto" +o:value("auto") +o:value("packet-up") +o:value("stream-up") +o:value("stream-one") + +o = s:option(Value, "xhttp_host", translate("XHTTP Host")) +o:depends({ transport = "xhttp" }) + +o = s:option(Value, "xhttp_path", translate("XHTTP Path")) +o.placeholder = "/" +o:depends({ transport = "xhttp" }) + +o = s:option(Flag, "use_xhttp_extra", translate("XHTTP Extra")) +o.default = "0" +o:depends({ transport = "xhttp" }) + +o = s:option(TextValue, "xhttp_extra", " ", translate("An XHttpObject in JSON format, used for sharing.")) +o:depends({ use_xhttp_extra = true }) +o.rows = 10 +o.wrap = "off" +o.datatype = "json" +local o_validate = o.validate +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n") + local v = o_validate(self, value) + if v then return v end + return nil, "XHTTP Extra " .. translate("Must be JSON text!") +end +o.custom_cfgvalue = function(self, section, value) + local raw = m:get(section, "xhttp_extra") + if raw then + return api.base64Decode(raw) + end +end +o.custom_write = function(self, section, value) + m:set(section, "xhttp_extra", api.base64Encode(value) or "") + local success, data = pcall(api.jsonc.parse, value) + if success and data then + local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address) + or (data.downloadSettings and data.downloadSettings.address) + if address and address ~= "" then + address = address:gsub("^%[", ""):gsub("%]$", "") + m:set(section, "download_address", address) + else + m:del(section, "download_address") + end + else + m:del(section, "download_address") + end +end +o.custom_remove = function(self, section, value) + m:del(section, "xhttp_extra") + m:del(section, "download_address") +end + +-- [[ User-Agent ]]-- +o = s:option(Value, "user_agent", translate("User-Agent")) +o.default = "" +o:value("", translate("default")) +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome") +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox") +o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari") +o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge") +o:value("Go-http-client/1.1", "golang") +o:value("curl/7.68.0", "curl") +o:depends({ tcp_guise = "http" }) +o:depends({ transport = "ws" }) +o:depends({ transport = "httpupgrade" }) +o:depends({ transport = "xhttp" }) +o:depends({ transport = "grpc" }) + +-- [[ Mux.Cool ]]-- +o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool")) +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless", transport = "raw" }) +o:depends({ protocol = "vless", transport = "ws" }) +o:depends({ protocol = "vless", transport = "grpc" }) +o:depends({ protocol = "vless", transport = "httpupgrade" }) +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +o = s:option(Value, "mux_concurrency", translate("Mux concurrency")) +o.default = -1 +o:depends({ mux = true }) + +o = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency")) +o.default = 8 +o:depends({ mux = true }) + +--[[FinalMask]] +o = s:option(Flag, "use_finalmask", "FinalMask") +o.default = "0" +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "wireguard" }) +o:depends({ protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(TextValue, "finalmask", " ") +o:depends({ use_finalmask = true }) +o.rows = 10 +o.wrap = "off" +o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" .. + translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.") +o.datatype = "json" +local o_validate = o.validate +o.validate = function(self, value) + value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n") + local v = o_validate(self, value) + if v then return v end + return nil, "FinalMask " .. translate("Must be JSON text!") +end +o.custom_cfgvalue = function(self, section, value) + local raw = m:get(section, "finalmask") + if raw then + return api.base64Decode(raw) + end +end +o.custom_write = function(self, section, value) + m:set(section, "finalmask", api.base64Encode(value) or "") +end + +--[[Fast Open]] +o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 + +--[[tcpMptcp]] +o = s:option(Flag, "tcpMptcp", "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration.")) +o.default = 0 + +o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution.")) +o:value("", translate("Auto")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("https", "HTTPS") + +o = s:option(Value, "domain_resolver_dns", "DNS") +o.datatype = "or(ipaddr,ipaddrport)" +o:value("114.114.114.114") +o:value("223.5.5.5:53") +o.default = o.keylist[1] +o:depends({ domain_resolver = "tcp" }) +o:depends({ domain_resolver = "udp" }) + +o = s:option(Value, "domain_resolver_dns_https", "DNS") +o:value("https://120.53.53.53/dns-query", "DNSPod") +o:value("https://223.5.5.5/dns-query", "AliDNS") +o.default = o.keylist[1] +o:depends({ domain_resolver = "https" }) + +o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect.")) +o.default = "" +o:value("", translate("Auto")) +o:value("UseIPv4", translate("IPv4 Only")) +o:value("UseIPv6", translate("IPv6 Only")) + +o = s:option(Flag, "happy_eyeballs", translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection.")) +o.default = 0 + +local protocols = s.fields["protocol"].keylist +if #protocols > 0 then + for i, v in ipairs(protocols) do + if not v:find("^_") then + local depends_condition = { protocol = v } + if v == "hysteria2" then + depends_condition["hysteria2_realms"] = false + end + s.fields["address"]:depends(depends_condition) + s.fields["port"]:depends(depends_condition) + s.fields["domain_resolver"]:depends(depends_condition) + s.fields["happy_eyeballs"]:depends(depends_condition) + + local strategy_depends = api.clone(depends_condition) + strategy_depends["happy_eyeballs"] = false + s.fields["domain_strategy"]:depends(strategy_depends) + + if v ~= "hysteria2" then + s.fields["tcp_fast_open"]:depends({ protocol = v }) + s.fields["tcpMptcp"]:depends({ protocol = v }) + end + end + end +end +end +-- [[ Normal single node End ]] + +if not load_shunt_options then + o = s:option(ListValue, "chain_proxy", translate("Chain Proxy")) + o:value("", translate("Close(Not use)")) + if not (load_iface_options or load_balancing_options) then + -- Special node cannot be use pre-proxy. + o:value("1", translate("Preproxy Node")) + o:value("3", translate("Outbound Interface")) + end + o:value("2", translate("Landing Node")) + + o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy.")) + o1:depends({ chain_proxy = "1", hysteria2_realms = false }) + o1.template = m:template_path("/cbi/nodes_listvalue") + o1.group = {} + + o3 = s:option(Value, "outbound_iface", translate("Outbound Interface")) + o3:depends({ chain_proxy = "3" }) + o3:value("", translate("All")) + for _, d in ipairs(netdev_list) do + o3:value(d.name, d.label) + end + + o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy.")) + o2:depends({ chain_proxy = "2", hysteria2_realms = false }) + o2.template = m:template_path("/cbi/nodes_listvalue") + o2.group = {} + + for k1, v1 in pairs(node_list) do + if k1 ~= "shunt_list" and k1 ~= "iface_list" then + for i, v in ipairs(v1) do + if v.id ~= arg[1] then + o1:value(v.id, v.remark) + o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if k1 == "normal_list" then + -- Landing Node not support use special node. + o2:value(v.id, v.remark) + o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + end + end + end + end +end + +api.luci_types(s1, s) + +if load_shunt_options then + local current_node = m:get(arg[1]) or {} + local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") + setfenv(shunt_lua, getfenv(1))(m, s1, { + node_id = arg[1], + node = current_node, + node_list = node_list, + }) +end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/3_ss-rust.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/3_ss-rust.lua index e3bc6907..f4fe81f2 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/3_ss-rust.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/3_ss-rust.lua @@ -1,68 +1,68 @@ -if not api.is_finded("sslocal") then - return -end - --- [[ Shadowsocks Rust ]] -local m, s1 = ... -local type_name = "SS-Rust" - -s1.fields["type"]:value(type_name, "Shadowsocks Rust") - -if s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "ssrust_" - -local ssrust_encrypt_method_list = { - "none", "plain", - "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", - "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" -} - -o = s:option(Value, "address", translate("Address (Support Domain Name)")) - -o = s:option(Value, "port", translate("Port")) -o.datatype = "port" - -o = s:option(Value, "password", translate("Password")) -o.password = true - -o = s:option(Value, "method", translate("Encrypt Method")) -for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end - -o = s:option(Value, "timeout", translate("Connection Timeout")) -o.datatype = "uinteger" -o.default = 300 - -o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) -o.default = 0 - -o = s:option(Flag, "plugin_enabled", translate("plugin")) -o.default = 0 - -o = s:option(Value, "plugin", "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed.")) -o.default = "none" -o:value("none", translate("none")) -if api.is_finded("xray-plugin") then o:value("xray-plugin") end -if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end -if api.is_finded("obfs-local") then o:value("obfs-local") end -if api.is_finded("shadow-tls") then o:value("shadow-tls") end -o:depends({ plugin_enabled = true }) -o.validate = function(self, value, t) - if value and value ~= "" and value ~= "none" then - if not api.is_finded(value) then - return nil, value .. ": " .. translate("Can't find this file!") - else - return value - end - end - return nil -end - -o = s:option(Value, "plugin_opts", translate("opts")) -o:depends({ plugin_enabled = true }) - +if not api.is_finded("sslocal") then + return +end + +-- [[ Shadowsocks Rust ]] +local m, s1 = ... +local type_name = "SS-Rust" + +s1.fields["type"]:value(type_name, "Shadowsocks Rust") + +if s1.val["type"] ~= type_name then + return +end + +local s = NamedSection(m, arg[1], "server") +s.type_name = type_name +s.option_prefix = "ssrust_" + +local ssrust_encrypt_method_list = { + "none", "plain", + "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" +} + +o = s:option(Value, "address", translate("Address (Support Domain Name)")) + +o = s:option(Value, "port", translate("Port")) +o.datatype = "port" + +o = s:option(Value, "password", translate("Password")) +o.password = true + +o = s:option(Value, "method", translate("Encrypt Method")) +for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end + +o = s:option(Value, "timeout", translate("Connection Timeout")) +o.datatype = "uinteger" +o.default = 300 + +o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) +o.default = 0 + +o = s:option(Flag, "plugin_enabled", translate("plugin")) +o.default = 0 + +o = s:option(Value, "plugin", "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed.")) +o.default = "none" +o:value("none", translate("none")) +if api.is_finded("xray-plugin") then o:value("xray-plugin") end +if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end +if api.is_finded("obfs-local") then o:value("obfs-local") end +if api.is_finded("shadow-tls") then o:value("shadow-tls") end +o:depends({ plugin_enabled = true }) +o.validate = function(self, value, t) + if value and value ~= "" and value ~= "none" then + if not api.is_finded(value) then + return nil, value .. ": " .. translate("Can't find this file!") + else + return value + end + end + return nil +end + +o = s:option(Value, "plugin_opts", translate("opts")) +o:depends({ plugin_enabled = true }) + api.luci_types(s1, s) \ No newline at end of file diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/4_ssr.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/4_ssr.lua index 4d18c36e..c9f281e1 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/4_ssr.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/4_ssr.lua @@ -1,66 +1,66 @@ -if not api.is_finded("ssr-local") then - return -end - --- [[ ShadowsocksR Libev ]] -local m, s1 = ... -local type_name = "SSR" - -s1.fields["type"]:value(type_name, "ShadowsocksR Libev") - -if s1.val["type"] ~= type_name then - return -end - -local s = NamedSection(m, arg[1], "server") -s.type_name = type_name -s.option_prefix = "ssr_" - -local ssr_encrypt_method_list = { - "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", - "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", - "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", - "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", - "chacha20-ietf" -} - -local ssr_protocol_list = { - "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", - "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", - "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", - "auth_chain_d", "auth_chain_e", "auth_chain_f" -} -local ssr_obfs_list = { - "plain", "http_simple", "http_post", "random_head", "tls_simple", - "tls1.0_session_auth", "tls1.2_ticket_auth" -} - -o = s:option(Value, "address", translate("Address (Support Domain Name)")) - -o = s:option(Value, "port", translate("Port")) -o.datatype = "port" - -o = s:option(Value, "password", translate("Password")) -o.password = true - -o = s:option(ListValue, "method", translate("Encrypt Method")) -for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end - -o = s:option(ListValue, "protocol", translate("Protocol")) -for a, t in ipairs(ssr_protocol_list) do o:value(t) end - -o = s:option(Value, "protocol_param", translate("Protocol_param")) - -o = s:option(ListValue, "obfs", translate("Obfs")) -for a, t in ipairs(ssr_obfs_list) do o:value(t) end - -o = s:option(Value, "obfs_param", translate("Obfs_param")) - -o = s:option(Value, "timeout", translate("Connection Timeout")) -o.datatype = "uinteger" -o.default = 300 - -o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) -o.default = 0 - +if not api.is_finded("ssr-local") then + return +end + +-- [[ ShadowsocksR Libev ]] +local m, s1 = ... +local type_name = "SSR" + +s1.fields["type"]:value(type_name, "ShadowsocksR Libev") + +if s1.val["type"] ~= type_name then + return +end + +local s = NamedSection(m, arg[1], "server") +s.type_name = type_name +s.option_prefix = "ssr_" + +local ssr_encrypt_method_list = { + "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", + "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", + "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", + "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", + "chacha20-ietf" +} + +local ssr_protocol_list = { + "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", + "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", + "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", + "auth_chain_d", "auth_chain_e", "auth_chain_f" +} +local ssr_obfs_list = { + "plain", "http_simple", "http_post", "random_head", "tls_simple", + "tls1.0_session_auth", "tls1.2_ticket_auth" +} + +o = s:option(Value, "address", translate("Address (Support Domain Name)")) + +o = s:option(Value, "port", translate("Port")) +o.datatype = "port" + +o = s:option(Value, "password", translate("Password")) +o.password = true + +o = s:option(ListValue, "method", translate("Encrypt Method")) +for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end + +o = s:option(ListValue, "protocol", translate("Protocol")) +for a, t in ipairs(ssr_protocol_list) do o:value(t) end + +o = s:option(Value, "protocol_param", translate("Protocol_param")) + +o = s:option(ListValue, "obfs", translate("Obfs")) +for a, t in ipairs(ssr_obfs_list) do o:value(t) end + +o = s:option(Value, "obfs_param", translate("Obfs_param")) + +o = s:option(Value, "timeout", translate("Connection Timeout")) +o.datatype = "uinteger" +o.default = 300 + +o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required")) +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/server_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua index 816d911d..4b5a9c5c 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/server_config.lua @@ -39,7 +39,7 @@ for filename in api.fs.dir(types_dir) do table.insert(type_table, filename) end table.sort(type_table, function(a, b) - return a < b + return a < b end) for index, value in ipairs(type_table) do 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 fd345466..972af357 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 @@ -1,534 +1,534 @@ -local singbox_bin = api.finded_com("sing-box") - -if not singbox_bin then - return -end - --- [[ Sing-Box ]] -local m, s1 = ... -local type_name = "sing-box" - -s1.fields["type"]:value(type_name, "Sing-Box") -if not s1.fields["type"].default then - s1.fields["type"].default = type_name -end - -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 = "singbox_" - -local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") - -local ss_method_list = { - "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", - "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" -} - -o = s:option(Flag, "custom", translate("Use Custom Config")) - -o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") -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(ListValue, "protocol", translate("Protocol")) -o:value("mixed", "Mixed") -o:value("socks", "Socks") -o:value("http", "HTTP") -o:value("shadowsocks", "Shadowsocks") -o:value("vmess", "Vmess") -o:value("vless", "VLESS") -o:value("trojan", "Trojan") -o:value("naive", "Naive") -if singbox_tags:find("with_quic") then - o:value("hysteria", "Hysteria") -end -if singbox_tags:find("with_quic") then - o:value("tuic", "TUIC") -end -if singbox_tags:find("with_quic") then - o:value("hysteria2", "Hysteria2") -end -o:value("anytls", "AnyTLS") -if singbox_tags:find("with_wireguard") then - o:value("wireguard", "WireGuard") -end -o:value("direct", "Direct") -o:depends({ custom = false }) - -o = s:option(DummyValue, "is_endpoint", "") -o.not_rewrite = true -o.template = m:template_path("/cbi/hidevalue") -o.value = "1" -o:depends({ custom = false, protocol = "wireguard" }) - -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({ protocol = "mixed" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "http" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "naive" }) -o:depends({ protocol = "hysteria" }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) -o:depends({ protocol = "anytls" }) -o:depends({ protocol = "wireguard" }) - -if singbox_tags:find("with_quic") then - o = s:option(Value, "hysteria_obfs", translate("Obfs Password")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps")) - o.default = "100" - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps")) - o.default = "100" - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_recv_window_client", translate("QUIC connection receive window")) - o:depends({ protocol = "hysteria" }) - - o = s:option(Value, "hysteria_max_conn_client", translate("QUIC concurrent bidirectional streams")) - o.default = "1024" - o:depends({ protocol = "hysteria" }) - - o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection")) - o:depends({ protocol = "hysteria" }) -end - -if singbox_tags:find("with_quic") then - o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm")) - o.default = "cubic" - o:value("bbr", translate("BBR")) - o:value("cubic", translate("CUBIC")) - o:value("new_reno", translate("New Reno")) - o:depends({ protocol = "tuic" }) - - o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake")) - o.default = 0 - o:depends({ protocol = "tuic" }) - - o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)")) - o.datatype = "uinteger" - o.default = "3" - o:depends({ protocol = "tuic" }) - - o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN")) - o.default = "default" - o:value("default", translate("Default")) - o:value("h3") - o:value("h2") - o:value("h3,h2") - o:value("http/1.1") - o:value("h2,http/1.1") - o:value("h3,h2,http/1.1") - o:value("spdy/3.1") - o:value("h3,spdy/3.1") - o:depends({ protocol = "tuic" }) -end - -if singbox_tags:find("with_quic") then - o = s:option(Flag, "hysteria2_realms", translate("Realms")) - o.default = "0" - o:depends({ protocol = "hysteria2"}) - - o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") - o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) - o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } - o:depends({ hysteria2_realms = "1" }) - - o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) - o:value("", translate("Disable")) - o:value("salamander") - o:value("gecko") - o:depends({ protocol = "hysteria2" }) - - o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) - o:depends({ hysteria2_obfs_type = "salamander" }) - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) - o.datatype = "uinteger" - o.placeholder = "512" - o.default = "512" - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) - o.datatype = "uinteger" - o.placeholder = "1200" - o.default = "1200" - o:depends({ hysteria2_obfs_type = "gecko" }) - - o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm")) - o.default = 0 - o:depends({ protocol = "hysteria2" }) - - o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) - o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) - - o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) - o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) -end - -o = s:option(ListValue, "d_protocol", translate("Destination protocol")) -o:value("tcp", "TCP") -o:value("udp", "UDP") -o:value("tcp,udp", "TCP,UDP") -o:depends({ protocol = "direct" }) - -o = s:option(Value, "d_address", translate("Destination address")) -o:depends({ protocol = "direct" }) - -o = s:option(Value, "d_port", translate("Destination port")) -o.datatype = "port" -o:depends({ protocol = "direct" }) - -o = s:option(Value, "decryption", translate("Encrypt Method")) -o.default = "none" -o:depends({ protocol = "vless" }) - -o = s:option(ListValue, "ss_method", translate("Encrypt Method")) -for a, t in ipairs(ss_method_list) do o:value(t) end -o:depends({ protocol = "shadowsocks" }) - -o = s:option(Value, "ss_password", translate("Password")) -o:depends({ protocol = "shadowsocks" }) - -o = s:option(ListValue, "flow", translate("flow")) -o.default = "" -o:value("", translate("Disable")) -o:value("xtls-rprx-vision") -o:depends({ protocol = "vless" , tls = true }) - -o = s:option(Flag, "tls", translate("TLS")) -o.default = 0 -o.validate = function(self, value, t) - if value then - local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil - if reality and reality == "1" then return value end - if value == "1" then - local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or "" - local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or "" - if ca == "" or key == "" then - return nil, translate("Public key and Private key path can not be empty!") - end - end - return value - end -end -o:depends({ protocol = "http" }) -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "anytls" }) - --- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab -if singbox_tags:find("with_utls") then - -- [[ REALITY ]] -- - o = s:option(Flag, "reality", translate("REALITY")) - o.default = 0 - o:depends({ protocol = "http", tls = true }) - o:depends({ protocol = "vmess", tls = true }) - o:depends({ protocol = "vless", tls = true }) - o:depends({ protocol = "trojan", tls = true }) - o:depends({ protocol = "anytls", tls = true }) - - o = s:option(Value, "reality_private_key", translate("Private Key")) - o:depends({ reality = true }) - - o = s:option(Value, "reality_shortId", translate("Short Id")) - o:depends({ reality = true }) - - o = s:option(Value, "reality_handshake_server", translate("Handshake Server")) - o.default = "google.com" - o:depends({ reality = true }) - - o = s:option(Value, "reality_handshake_server_port", translate("Handshake Server Port")) - o.datatype = "port" - o.default = "443" - o:depends({ reality = true }) -end - -o = s:option(ListValue, "alpn", translate("ALPN")) -o.default = "default" -o:value("default", translate("Default")) -o:value("h3") -o:value("h2") -o:value("h3,h2") -o:value("http/1.1") -o:value("h2,http/1.1") -o:value("h3,h2,http/1.1") -o:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria" }) - --- [[ TLS ]] -- - -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:depends({ tls = true, reality = false }) -o:depends({ protocol = "naive" }) -o:depends({ protocol = "hysteria" }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) -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 = 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:depends({ tls = true, reality = false }) -o:depends({ protocol = "naive" }) -o:depends({ protocol = "hysteria" }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2" }) -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 = s:option(Flag, "ech", translate("ECH")) -o.default = "0" -o:depends({ tls = true, flow = "", reality = false }) -o:depends({ protocol = "naive" }) -o:depends({ protocol = "hysteria" }) -o:depends({ protocol = "tuic" }) -o:depends({ protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(TextValue, "ech_key", translate("ECH Key")) -o.default = "" -o.rows = 5 -o.wrap = "off" -o:depends({ ech = true }) -o.validate = function(self, value) - value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n") - value = value:gsub("^%s*\n", "") - if value:sub(-1) == "\n" then - value = value:sub(1, -2) - end - return value -end - -o = s:option(ListValue, "transport", translate("Transport")) -o:value("tcp", "TCP") -o:value("http", "HTTP") -o:value("ws", "WebSocket") -o:value("httpupgrade", "HTTPUpgrade") -o:value("quic", "QUIC") -o:value("grpc", "gRPC") -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "trojan" }) - --- [[ HTTP ]]-- - -o = s:option(DynamicList, "http_host", translate("HTTP Host")) -o:depends({ transport = "http" }) - -o = s:option(Value, "http_path", translate("HTTP Path")) -o:depends({ transport = "http" }) - --- [[ WebSocket ]]-- - -o = s:option(Value, "ws_host", translate("WebSocket Host")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_path", translate("WebSocket Path")) -o:depends({ transport = "ws" }) - --- [[ HTTPUpgrade ]]-- - -o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) -o:depends({ transport = "httpupgrade" }) - -o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path")) -o:depends({ transport = "httpupgrade" }) - --- [[ gRPC ]]-- -o = s:option(Value, "grpc_serviceName", "ServiceName") -o:depends({ transport = "grpc" }) - --- [[ Mux ]]-- -o = s:option(Flag, "mux", translate("Mux")) -o.rmempty = false -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless", flow = "" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - --- [[ TCP Brutal ]]-- -o = s:option(Flag, "tcpbrutal", translate("TCP Brutal")) -o.default = 0 -o:depends({ mux = true }) - -o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps")) -o.default = "10" -o:depends({ tcpbrutal = true }) - -o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps")) -o.default = "50" -o:depends({ tcpbrutal = true }) - -if singbox_tags:find("with_wireguard") then - o = s:option(Flag, "wireguard_system_interface", translate("System interface")) - o.default = 0 - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_mtu", "MTU") - o.default = "1408" - o:depends({ protocol = "wireguard" }) - - o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_private_key", translate("Private Key")) - o.datatype = "base64" - o:depends({ protocol = "wireguard" }) - - o = s:option(Value, "wireguard_public_key", translate("Public Key")) - o.datatype = "base64" - o:depends({ protocol = "wireguard" }) - - o = s:option(DummyValue, "gen_wireguard_key") - o.template = m:template_path("/server/gen_wireguard_key") - o:depends({ protocol = "wireguard" }) -end - -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 }) - -local nodes_table = {} -for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" and e.type == type_name then - nodes_table[#nodes_table + 1] = { - id = e[".name"], - remarks = e["remark"], - group = e["group"] - } - end -end - -o = s:option(ListValue, "outbound_node", translate("outbound node")) -o:value("", translate("Close")) -o:value("_socks", translate("Custom Socks")) -o:value("_http", translate("Custom HTTP")) -o:value("_iface", translate("Custom Interface")) -o.template = m:template_path("/cbi/nodes_listvalue") -o.group = {"","","",""} -for k, v in pairs(nodes_table) do - o:value(v.id, v.remarks) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") -end -o:depends({ custom = false }) - -o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)")) -o:depends({ outbound_node = "_socks" }) -o:depends({ outbound_node = "_http" }) - -o = s:option(Value, "outbound_node_port", translate("Port")) -o.datatype = "port" -o:depends({ outbound_node = "_socks" }) -o:depends({ outbound_node = "_http" }) - -o = s:option(Value, "outbound_node_username", translate("Username")) -o:depends({ outbound_node = "_socks" }) -o:depends({ outbound_node = "_http" }) - -o = s:option(Value, "outbound_node_password", translate("Password")) -o.password = true -o:depends({ outbound_node = "_socks" }) -o:depends({ outbound_node = "_http" }) - -o = s:option(Value, "outbound_node_iface", translate("Interface")) -o:depends({ outbound_node = "_iface" }) -local netdev_list = api.get_network_devices() -for _, d in ipairs(netdev_list) do - o:value(d.name, d.label) -end - -o = s:option(Flag, "log", translate("Log")) -o.default = "1" -o.rmempty = false - -o = s:option(ListValue, "loglevel", translate("Log Level")) -o.default = "info" -o:value("debug") -o:value("info") -o:value("warn") -o:value("error") -o:depends({ log = true }) - +local singbox_bin = api.finded_com("sing-box") + +if not singbox_bin then + return +end + +-- [[ Sing-Box ]] +local m, s1 = ... +local type_name = "sing-box" + +s1.fields["type"]:value(type_name, "Sing-Box") +if not s1.fields["type"].default then + s1.fields["type"].default = type_name +end + +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 = "singbox_" + +local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") + +local ss_method_list = { + "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +o = s:option(Flag, "custom", translate("Use Custom Config")) + +o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") +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(ListValue, "protocol", translate("Protocol")) +o:value("mixed", "Mixed") +o:value("socks", "Socks") +o:value("http", "HTTP") +o:value("shadowsocks", "Shadowsocks") +o:value("vmess", "Vmess") +o:value("vless", "VLESS") +o:value("trojan", "Trojan") +o:value("naive", "Naive") +if singbox_tags:find("with_quic") then + o:value("hysteria", "Hysteria") +end +if singbox_tags:find("with_quic") then + o:value("tuic", "TUIC") +end +if singbox_tags:find("with_quic") then + o:value("hysteria2", "Hysteria2") +end +o:value("anytls", "AnyTLS") +if singbox_tags:find("with_wireguard") then + o:value("wireguard", "WireGuard") +end +o:value("direct", "Direct") +o:depends({ custom = false }) + +o = s:option(DummyValue, "is_endpoint", "") +o.not_rewrite = true +o.template = m:template_path("/cbi/hidevalue") +o.value = "1" +o:depends({ custom = false, protocol = "wireguard" }) + +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({ protocol = "mixed" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "http" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "naive" }) +o:depends({ protocol = "hysteria" }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) +o:depends({ protocol = "anytls" }) +o:depends({ protocol = "wireguard" }) + +if singbox_tags:find("with_quic") then + o = s:option(Value, "hysteria_obfs", translate("Obfs Password")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps")) + o.default = "100" + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps")) + o.default = "100" + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_recv_window_client", translate("QUIC connection receive window")) + o:depends({ protocol = "hysteria" }) + + o = s:option(Value, "hysteria_max_conn_client", translate("QUIC concurrent bidirectional streams")) + o.default = "1024" + o:depends({ protocol = "hysteria" }) + + o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection")) + o:depends({ protocol = "hysteria" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm")) + o.default = "cubic" + o:value("bbr", translate("BBR")) + o:value("cubic", translate("CUBIC")) + o:value("new_reno", translate("New Reno")) + o:depends({ protocol = "tuic" }) + + o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake")) + o.default = 0 + o:depends({ protocol = "tuic" }) + + o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)")) + o.datatype = "uinteger" + o.default = "3" + o:depends({ protocol = "tuic" }) + + o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN")) + o.default = "default" + o:value("default", translate("Default")) + o:value("h3") + o:value("h2") + o:value("h3,h2") + o:value("http/1.1") + o:value("h2,http/1.1") + o:value("h3,h2,http/1.1") + o:value("spdy/3.1") + o:value("h3,spdy/3.1") + o:depends({ protocol = "tuic" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(Flag, "hysteria2_realms", translate("Realms")) + o.default = "0" + o:depends({ protocol = "hysteria2"}) + + o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") + o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) + o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } + o:depends({ hysteria2_realms = "1" }) + + o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) + o:value("", translate("Disable")) + o:value("salamander") + o:value("gecko") + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) + o:depends({ hysteria2_obfs_type = "salamander" }) + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) + o.datatype = "uinteger" + o.placeholder = "512" + o.default = "512" + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) + o.datatype = "uinteger" + o.placeholder = "1200" + o.default = "1200" + o:depends({ hysteria2_obfs_type = "gecko" }) + + o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm")) + o.default = 0 + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) + o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) + + o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) + o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) +end + +o = s:option(ListValue, "d_protocol", translate("Destination protocol")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("tcp,udp", "TCP,UDP") +o:depends({ protocol = "direct" }) + +o = s:option(Value, "d_address", translate("Destination address")) +o:depends({ protocol = "direct" }) + +o = s:option(Value, "d_port", translate("Destination port")) +o.datatype = "port" +o:depends({ protocol = "direct" }) + +o = s:option(Value, "decryption", translate("Encrypt Method")) +o.default = "none" +o:depends({ protocol = "vless" }) + +o = s:option(ListValue, "ss_method", translate("Encrypt Method")) +for a, t in ipairs(ss_method_list) do o:value(t) end +o:depends({ protocol = "shadowsocks" }) + +o = s:option(Value, "ss_password", translate("Password")) +o:depends({ protocol = "shadowsocks" }) + +o = s:option(ListValue, "flow", translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ protocol = "vless" , tls = true }) + +o = s:option(Flag, "tls", translate("TLS")) +o.default = 0 +o.validate = function(self, value, t) + if value then + local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil + if reality and reality == "1" then return value end + if value == "1" then + local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or "" + local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or "" + if ca == "" or key == "" then + return nil, translate("Public key and Private key path can not be empty!") + end + end + return value + end +end +o:depends({ protocol = "http" }) +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "anytls" }) + +-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab +if singbox_tags:find("with_utls") then + -- [[ REALITY ]] -- + o = s:option(Flag, "reality", translate("REALITY")) + o.default = 0 + o:depends({ protocol = "http", tls = true }) + o:depends({ protocol = "vmess", tls = true }) + o:depends({ protocol = "vless", tls = true }) + o:depends({ protocol = "trojan", tls = true }) + o:depends({ protocol = "anytls", tls = true }) + + o = s:option(Value, "reality_private_key", translate("Private Key")) + o:depends({ reality = true }) + + o = s:option(Value, "reality_shortId", translate("Short Id")) + o:depends({ reality = true }) + + o = s:option(Value, "reality_handshake_server", translate("Handshake Server")) + o.default = "google.com" + o:depends({ reality = true }) + + o = s:option(Value, "reality_handshake_server_port", translate("Handshake Server Port")) + o.datatype = "port" + o.default = "443" + o:depends({ reality = true }) +end + +o = s:option(ListValue, "alpn", translate("ALPN")) +o.default = "default" +o:value("default", translate("Default")) +o:value("h3") +o:value("h2") +o:value("h3,h2") +o:value("http/1.1") +o:value("h2,http/1.1") +o:value("h3,h2,http/1.1") +o:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria" }) + +-- [[ TLS ]] -- + +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:depends({ tls = true, reality = false }) +o:depends({ protocol = "naive" }) +o:depends({ protocol = "hysteria" }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) +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 = 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:depends({ tls = true, reality = false }) +o:depends({ protocol = "naive" }) +o:depends({ protocol = "hysteria" }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2" }) +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 = s:option(Flag, "ech", translate("ECH")) +o.default = "0" +o:depends({ tls = true, flow = "", reality = false }) +o:depends({ protocol = "naive" }) +o:depends({ protocol = "hysteria" }) +o:depends({ protocol = "tuic" }) +o:depends({ protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(TextValue, "ech_key", translate("ECH Key")) +o.default = "" +o.rows = 5 +o.wrap = "off" +o:depends({ ech = true }) +o.validate = function(self, value) + value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n") + value = value:gsub("^%s*\n", "") + if value:sub(-1) == "\n" then + value = value:sub(1, -2) + end + return value +end + +o = s:option(ListValue, "transport", translate("Transport")) +o:value("tcp", "TCP") +o:value("http", "HTTP") +o:value("ws", "WebSocket") +o:value("httpupgrade", "HTTPUpgrade") +o:value("quic", "QUIC") +o:value("grpc", "gRPC") +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "trojan" }) + +-- [[ HTTP ]]-- + +o = s:option(DynamicList, "http_host", translate("HTTP Host")) +o:depends({ transport = "http" }) + +o = s:option(Value, "http_path", translate("HTTP Path")) +o:depends({ transport = "http" }) + +-- [[ WebSocket ]]-- + +o = s:option(Value, "ws_host", translate("WebSocket Host")) +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_path", translate("WebSocket Path")) +o:depends({ transport = "ws" }) + +-- [[ HTTPUpgrade ]]-- + +o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) +o:depends({ transport = "httpupgrade" }) + +o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path")) +o:depends({ transport = "httpupgrade" }) + +-- [[ gRPC ]]-- +o = s:option(Value, "grpc_serviceName", "ServiceName") +o:depends({ transport = "grpc" }) + +-- [[ Mux ]]-- +o = s:option(Flag, "mux", translate("Mux")) +o.rmempty = false +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless", flow = "" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +-- [[ TCP Brutal ]]-- +o = s:option(Flag, "tcpbrutal", translate("TCP Brutal")) +o.default = 0 +o:depends({ mux = true }) + +o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps")) +o.default = "10" +o:depends({ tcpbrutal = true }) + +o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps")) +o.default = "50" +o:depends({ tcpbrutal = true }) + +if singbox_tags:find("with_wireguard") then + o = s:option(Flag, "wireguard_system_interface", translate("System interface")) + o.default = 0 + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_mtu", "MTU") + o.default = "1408" + o:depends({ protocol = "wireguard" }) + + o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_private_key", translate("Private Key")) + o.datatype = "base64" + o:depends({ protocol = "wireguard" }) + + o = s:option(Value, "wireguard_public_key", translate("Public Key")) + o.datatype = "base64" + o:depends({ protocol = "wireguard" }) + + o = s:option(DummyValue, "gen_wireguard_key") + o.template = m:template_path("/server/gen_wireguard_key") + o:depends({ protocol = "wireguard" }) +end + +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 }) + +local nodes_table = {} +for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" and e.type == type_name then + nodes_table[#nodes_table + 1] = { + id = e[".name"], + remarks = e["remark"], + group = e["group"] + } + end +end + +o = s:option(ListValue, "outbound_node", translate("outbound node")) +o:value("", translate("Close")) +o:value("_socks", translate("Custom Socks")) +o:value("_http", translate("Custom HTTP")) +o:value("_iface", translate("Custom Interface")) +o.template = m:template_path("/cbi/nodes_listvalue") +o.group = {"","","",""} +for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") +end +o:depends({ custom = false }) + +o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)")) +o:depends({ outbound_node = "_socks" }) +o:depends({ outbound_node = "_http" }) + +o = s:option(Value, "outbound_node_port", translate("Port")) +o.datatype = "port" +o:depends({ outbound_node = "_socks" }) +o:depends({ outbound_node = "_http" }) + +o = s:option(Value, "outbound_node_username", translate("Username")) +o:depends({ outbound_node = "_socks" }) +o:depends({ outbound_node = "_http" }) + +o = s:option(Value, "outbound_node_password", translate("Password")) +o.password = true +o:depends({ outbound_node = "_socks" }) +o:depends({ outbound_node = "_http" }) + +o = s:option(Value, "outbound_node_iface", translate("Interface")) +o:depends({ outbound_node = "_iface" }) +local netdev_list = api.get_network_devices() +for _, d in ipairs(netdev_list) do + o:value(d.name, d.label) +end + +o = s:option(Flag, "log", translate("Log")) +o.default = "1" +o.rmempty = false + +o = s:option(ListValue, "loglevel", translate("Log Level")) +o.default = "info" +o:value("debug") +o:value("info") +o:value("warn") +o:value("error") +o:depends({ log = true }) + api.luci_types(s1, s) \ No newline at end of file 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 440102ac..30f99f39 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 @@ -1,552 +1,552 @@ -if not api.finded_com("xray") then - return -end - --- [[ Xray ]] -local m, s1 = ... -local type_name = "Xray" - -s1.fields["type"]:value(type_name, "Xray") -if not s1.fields["type"].default then - s1.fields["type"].default = type_name -end - -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 = "xray_" - -local ss_method_list = { - "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" -} - -local header_type_list = { - "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" -} - -o = s:option(Flag, "custom", translate("Use Custom Config")) - -o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") -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(ListValue, "protocol", translate("Protocol")) -o:value("vmess", "Vmess") -o:value("vless", "VLESS") -o:value("http", "HTTP") -o:value("socks", "Socks") -o:value("shadowsocks", "Shadowsocks") -o:value("trojan", "Trojan") -o:value("hysteria2", "Hysteria2") -o:value("wireguard", "WireGuard") -o:value("tunnel", "Tunnel") -o:depends({ custom = false }) - -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({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "trojan" }) -o:depends({ protocol = "hysteria2" }) -o:depends({ protocol = "wireguard" }) - -o = s:option(ListValue, "d_protocol", translate("Destination protocol")) -o:value("tcp", "TCP") -o:value("udp", "UDP") -o:value("tcp,udp", "TCP,UDP") -o:depends({ protocol = "tunnel" }) - -o = s:option(Value, "d_address", translate("Destination address")) -o:depends({ protocol = "tunnel" }) - -o = s:option(Value, "d_port", translate("Destination port")) -o.datatype = "port" -o:depends({ protocol = "tunnel" }) - -o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)") -o.default = "none" -o.placeholder = "none" -o:depends({ protocol = "vless" }) -o.validate = function(self, value) - value = api.trim(value) - return (value == "" and "none" or value) -end - -o = s:option(ListValue, "ss_method", translate("Encrypt Method")) -for a, t in ipairs(ss_method_list) do o:value(t) end -o:depends({ protocol = "shadowsocks" }) - -o = s:option(Value, "ss_password", translate("Password")) -o:depends({ protocol = "shadowsocks" }) - -o = s:option(ListValue, "ss_network", translate("Transport")) -o.default = "tcp,udp" -o:value("tcp", "TCP") -o:value("udp", "UDP") -o:value("tcp,udp", "TCP,UDP") -o:depends({ protocol = "shadowsocks" }) - -o = s:option(Flag, "udp_forward", translate("UDP Forward")) -o.default = "1" -o.rmempty = false -o:depends({ protocol = "socks" }) - -o = s:option(ListValue, "flow", translate("flow")) -o.default = "" -o:value("", translate("Disable")) -o:value("xtls-rprx-vision") -o:depends({ protocol = "vless" }) - ----- [[ hysteria2 ]] -o = s:option(Flag, "hysteria2_realms", translate("Realms")) -o.default = "0" -o:depends({ protocol = "hysteria2"}) - -o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") -o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) -o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } -o:depends({ hysteria2_realms = "1" }) - -o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) -o:value("", translate("Disable")) -o:value("salamander") -o:value("gecko") -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) -o:depends({ hysteria2_obfs_type = "salamander" }) -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) -o.datatype = "uinteger" -o.placeholder = "512" -o.default = "512" -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) -o.datatype = "uinteger" -o.placeholder = "1200" -o.default = "1200" -o:depends({ hysteria2_obfs_type = "gecko" }) - -o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control")) -o.default = 0 -o:depends({ protocol = "hysteria2" }) - -o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) -o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) - -o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) -o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) - ----- [[ TLS ]] -o = s:option(Flag, "tls", "TLS") -o.default = 0 -o.validate = function(self, value, t) - if value then - local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil - if reality and reality == "1" then return value end - if value == "1" then - local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or "" - local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or "" - if ca == "" or key == "" then - return nil, translate("Public key and Private key path can not be empty!") - end - end - return value - end -end -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "http" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - --- [[ REALITY ]] -- -o = s:option(Flag, "reality", translate("REALITY")) -o.default = 0 -o:depends({ tls = true }) - -o = s:option(Value, "reality_private_key", translate("Private Key")) -o:depends({ reality = true }) - -o = s:option(DynamicList, "reality_shortId", translate("Short Id")) -o:depends({ reality = true }) - -o = s:option(Value, "reality_dest", translate("Dest")) -o.default = "google.com:443" -o:depends({ reality = true }) - -o = s:option(DynamicList, "reality_serverNames", translate("serverNames")) -o:depends({ reality = true }) - -o = s:option(ListValue, "alpn", translate("alpn")) -o.default = "default" -o:value("default", translate("Default")) -o:value("h3") -o:value("h2") -o:value("h3,h2") -o:value("http/1.1") -o:value("h2,http/1.1") -o:value("h3,h2,http/1.1") -o:depends({ tls = true, reality = false }) - -o = s:option(Flag, "use_mldsa65Seed", translate("ML-DSA-65")) -o.default = "0" -o:depends({ reality = true }) - -o = s:option(TextValue, "reality_mldsa65Seed", "ML-DSA-65 " .. translate("Private Key")) -o.default = "" -o.rows = 5 -o.wrap = "soft" -o:depends({ use_mldsa65Seed = true }) -o.validate = function(self, value) - return api.trim(value:gsub("[\r\n]", "")) -end - --- o = s:option(Value, "minversion", translate("minversion")) --- o.default = "1.3" --- o:value("1.3") ---o:depends({ tls = true }) - --- [[ TLS ]] -- -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:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2"}) -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 = 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:depends({ tls = true, reality = false }) -o:depends({ protocol = "hysteria2"}) -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 = s:option(Flag, "ech", translate("ECH")) -o.default = "0" -o:depends({ tls = true, reality = false }) - -o = s:option(TextValue, "ech_key", translate("ECH Key")) -o.default = "" -o.rows = 5 -o.wrap = "soft" -o:depends({ ech = true }) -o.validate = function(self, value) - return api.trim(value:gsub("[\r\n]", "")) -end - -o = s:option(ListValue, "transport", translate("Transport")) -o:value("raw", "RAW") -o:value("mkcp", "mKCP") -o:value("ws", "WebSocket") -o:value("grpc", "gRPC") -o:value("httpupgrade", "HttpUpgrade") -o:value("xhttp", "XHTTP") -o:depends({ protocol = "vmess" }) -o:depends({ protocol = "vless" }) -o:depends({ protocol = "socks" }) -o:depends({ protocol = "shadowsocks" }) -o:depends({ protocol = "trojan" }) - --- [[ WebSocket ]]-- -o = s:option(Value, "ws_host", translate("WebSocket Host")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_path", translate("WebSocket Path")) -o:depends({ transport = "ws" }) - --- [[ HttpUpgrade ]]-- -o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host")) -o:depends({ transport = "httpupgrade" }) - -o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path")) -o.placeholder = "/" -o:depends({ transport = "httpupgrade" }) - --- [[ XHTTP ]]-- -o = s:option(Value, "xhttp_host", translate("XHTTP Host")) -o:depends({ transport = "xhttp" }) - -o = s:option(Value, "xhttp_path", translate("XHTTP Path")) -o.placeholder = "/" -o:depends({ transport = "xhttp" }) - -o = s:option(Value, "xhttp_maxuploadsize", translate("maxUploadSize")) -o.default = "1000000" -o:depends({ transport = "xhttp" }) - -o = s:option(Value, "xhttp_maxconcurrentuploads", translate("maxConcurrentUploads")) -o.default = "10" -o:depends({ transport = "xhttp" }) - -o = s:option(Value, "splithttp_maxuploadsize", translate("maxUploadSize")) -o.default = "1000000" -o:depends({ transport = "splithttp" }) - -o = s:option(Value, "splithttp_maxconcurrentuploads", translate("maxConcurrentUploads")) -o.default = "10" -o:depends({ transport = "splithttp" }) - --- [[ TCP ]]-- - -o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) -o:value("none", "none") -o:value("http", "http") -o:depends({ transport = "raw" }) - -o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) -o:depends({ tcp_guise = "http" }) - -o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) -o:depends({ tcp_guise = "http" }) - --- [[ mKCP ]]-- - -o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) -for a, t in ipairs(header_type_list) do o:value(t) end -o:depends({ transport = "mkcp" }) - -o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) -o:depends({ mkcp_guise = "dns" }) - -o = s:option(Value, "mkcp_mtu", translate("KCP MTU")) -o.datatype = "uinteger" -o.default = 1350 -o:depends({ transport = "mkcp" }) - -o = s:option(Value, "mkcp_seed", translate("KCP Seed")) -o:depends({ transport = "mkcp" }) - --- [[ gRPC ]]-- -o = s:option(Value, "grpc_serviceName", "ServiceName") -o:depends({ transport = "grpc" }) - ---[[FinalMask]] -o = s:option(Flag, "use_finalmask", "FinalMask") -o.default = "0" -o:depends({ custom = false, protocol = "vmess" }) -o:depends({ custom = false, protocol = "vless" }) -o:depends({ custom = false, protocol = "trojan" }) -o:depends({ custom = false, protocol = "shadowsocks" }) -o:depends({ custom = false, protocol = "hysteria2", hysteria2_realms = false }) - -o = s:option(TextValue, "finalmask", "FinalMask JSON") -o:depends({ use_finalmask = true }) -o.rows = 10 -o.wrap = "off" -o.datatype = "json" -o.custom_cfgvalue = function(self, section, value) - local raw = m:get(section, "finalmask") - if raw then - return api.base64Decode(raw) - end -end -o.custom_write = function(self, section, value) - m:set(section, "finalmask", api.base64Encode(value) or "") -end - ---[[acceptProxyProtocol]] -o = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used.")) -o.default = "0" -o:depends({ transport = "raw" }) -o:depends({ transport = "ws" }) - ---[[Fast Open]] -o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) -o.default = "0" -o:depends({ protocol = "vmess", custom = false }) -o:depends({ protocol = "vless", custom = false }) -o:depends({ protocol = "shadowsocks", custom = false }) -o:depends({ protocol = "trojan", custom = false }) - --- [[ Fallback ]]-- -o = s:option(Flag, "fallback", translate("Fallback")) -o:depends({ protocol = "vless", transport = "raw" }) -o:depends({ protocol = "trojan", transport = "raw" }) - ---[[ -o = s:option(Value, "fallback_alpn", "Fallback alpn") -o:depends({ fallback = true }) - -o = s:option(Value, "fallback_path", "Fallback path") -o:depends({ fallback = true }) - -o = s:option(Value, "fallback_dest", "Fallback dest") -o:depends({ fallback = true }) - -o = s:option(Value, "fallback_xver", "Fallback xver") -o.default = 0 -o:depends({ fallback = true }) -]]-- - -o = s:option(DynamicList, "fallback_list", "Fallback", translate("format: dest,path,xver")) -o:depends({ fallback = true }) - --- Not supported at present ---[[ -o = s:option(Flag, "wireguard_system_interface", translate("System interface")) -o.default = 0 -o:depends({ protocol = "wireguard" }) -]]-- - -o = s:option(Value, "wireguard_mtu", "MTU") -o.default = "1420" -o:depends({ protocol = "wireguard" }) - --- Not supported at present ---[[ -o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) -o:depends({ protocol = "wireguard" }) -]]-- - -o = s:option(Value, "wireguard_private_key", translate("Private Key")) -o.datatype = "base64" -o:depends({ protocol = "wireguard" }) - -o = s:option(Value, "wireguard_public_key", translate("Public Key")) -o.datatype = "base64" -o:depends({ protocol = "wireguard" }) - -o = s:option(DummyValue, "gen_wireguard_key") -o.template = m:template_path("/server/gen_wireguard_key") -o:depends({ protocol = "wireguard" }) - -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 }) - -local nodes_table = {} -for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" and e.type == type_name then - nodes_table[#nodes_table + 1] = { - id = e[".name"], - remarks = e["remark"], - group = e["group"] - } - end -end - -o = s:option(ListValue, "outbound_node", translate("outbound node")) -o:value("", translate("Close")) -o:value("_socks", translate("Custom Socks")) -o:value("_http", translate("Custom HTTP")) -o:value("_iface", translate("Custom Interface")) -o.template = m:template_path("/cbi/nodes_listvalue") -o.group = {"","","",""} -for k, v in pairs(nodes_table) do - o:value(v.id, v.remarks) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") -end -o:depends({ custom = false }) - -o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)")) -o:depends({ outbound_node = "_socks"}) -o:depends({ outbound_node = "_http"}) - -o = s:option(Value, "outbound_node_port", translate("Port")) -o.datatype = "port" -o:depends({ outbound_node = "_socks"}) -o:depends({ outbound_node = "_http"}) - -o = s:option(Value, "outbound_node_username", translate("Username")) -o:depends({ outbound_node = "_socks"}) -o:depends({ outbound_node = "_http"}) - -o = s:option(Value, "outbound_node_password", translate("Password")) -o.password = true -o:depends({ outbound_node = "_socks"}) -o:depends({ outbound_node = "_http"}) - -o = s:option(Value, "outbound_node_iface", translate("Interface")) -o:depends({ outbound_node = "_iface"}) -local netdev_list = api.get_network_devices() -for _, d in ipairs(netdev_list) do - o:value(d.name, d.label) -end - -o = s:option(Flag, "log", translate("Log")) -o.default = "1" -o.rmempty = false - -o = s:option(ListValue, "loglevel", translate("Log Level")) -o.default = "warning" -o:value("debug") -o:value("info") -o:value("warning") -o:value("error") -o:depends({ log = true }) - +if not api.finded_com("xray") then + return +end + +-- [[ Xray ]] +local m, s1 = ... +local type_name = "Xray" + +s1.fields["type"]:value(type_name, "Xray") +if not s1.fields["type"].default then + s1.fields["type"].default = type_name +end + +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 = "xray_" + +local ss_method_list = { + "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +local header_type_list = { + "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns" +} + +o = s:option(Flag, "custom", translate("Use Custom Config")) + +o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") +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(ListValue, "protocol", translate("Protocol")) +o:value("vmess", "Vmess") +o:value("vless", "VLESS") +o:value("http", "HTTP") +o:value("socks", "Socks") +o:value("shadowsocks", "Shadowsocks") +o:value("trojan", "Trojan") +o:value("hysteria2", "Hysteria2") +o:value("wireguard", "WireGuard") +o:value("tunnel", "Tunnel") +o:depends({ custom = false }) + +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({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "trojan" }) +o:depends({ protocol = "hysteria2" }) +o:depends({ protocol = "wireguard" }) + +o = s:option(ListValue, "d_protocol", translate("Destination protocol")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("tcp,udp", "TCP,UDP") +o:depends({ protocol = "tunnel" }) + +o = s:option(Value, "d_address", translate("Destination address")) +o:depends({ protocol = "tunnel" }) + +o = s:option(Value, "d_port", translate("Destination port")) +o.datatype = "port" +o:depends({ protocol = "tunnel" }) + +o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)") +o.default = "none" +o.placeholder = "none" +o:depends({ protocol = "vless" }) +o.validate = function(self, value) + value = api.trim(value) + return (value == "" and "none" or value) +end + +o = s:option(ListValue, "ss_method", translate("Encrypt Method")) +for a, t in ipairs(ss_method_list) do o:value(t) end +o:depends({ protocol = "shadowsocks" }) + +o = s:option(Value, "ss_password", translate("Password")) +o:depends({ protocol = "shadowsocks" }) + +o = s:option(ListValue, "ss_network", translate("Transport")) +o.default = "tcp,udp" +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("tcp,udp", "TCP,UDP") +o:depends({ protocol = "shadowsocks" }) + +o = s:option(Flag, "udp_forward", translate("UDP Forward")) +o.default = "1" +o.rmempty = false +o:depends({ protocol = "socks" }) + +o = s:option(ListValue, "flow", translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ protocol = "vless" }) + +---- [[ hysteria2 ]] +o = s:option(Flag, "hysteria2_realms", translate("Realms")) +o.default = "0" +o:depends({ protocol = "hysteria2"}) + +o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name") +o:depends({ hysteria2_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, "hysteria2_realm_stun", translate("Realm STUN")) +o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" } +o:depends({ hysteria2_realms = "1" }) + +o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type")) +o:value("", translate("Disable")) +o:value("salamander") +o:value("gecko") +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password")) +o:depends({ hysteria2_obfs_type = "salamander" }) +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)")) +o.datatype = "uinteger" +o.placeholder = "512" +o.default = "512" +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)")) +o.datatype = "uinteger" +o.placeholder = "1200" +o.default = "1200" +o:depends({ hysteria2_obfs_type = "gecko" }) + +o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control")) +o.default = 0 +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps")) +o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) + +o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) +o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false }) + +---- [[ TLS ]] +o = s:option(Flag, "tls", "TLS") +o.default = 0 +o.validate = function(self, value, t) + if value then + local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil + if reality and reality == "1" then return value end + if value == "1" then + local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or "" + local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or "" + if ca == "" or key == "" then + return nil, translate("Public key and Private key path can not be empty!") + end + end + return value + end +end +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "http" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +-- [[ REALITY ]] -- +o = s:option(Flag, "reality", translate("REALITY")) +o.default = 0 +o:depends({ tls = true }) + +o = s:option(Value, "reality_private_key", translate("Private Key")) +o:depends({ reality = true }) + +o = s:option(DynamicList, "reality_shortId", translate("Short Id")) +o:depends({ reality = true }) + +o = s:option(Value, "reality_dest", translate("Dest")) +o.default = "google.com:443" +o:depends({ reality = true }) + +o = s:option(DynamicList, "reality_serverNames", translate("serverNames")) +o:depends({ reality = true }) + +o = s:option(ListValue, "alpn", translate("alpn")) +o.default = "default" +o:value("default", translate("Default")) +o:value("h3") +o:value("h2") +o:value("h3,h2") +o:value("http/1.1") +o:value("h2,http/1.1") +o:value("h3,h2,http/1.1") +o:depends({ tls = true, reality = false }) + +o = s:option(Flag, "use_mldsa65Seed", translate("ML-DSA-65")) +o.default = "0" +o:depends({ reality = true }) + +o = s:option(TextValue, "reality_mldsa65Seed", "ML-DSA-65 " .. translate("Private Key")) +o.default = "" +o.rows = 5 +o.wrap = "soft" +o:depends({ use_mldsa65Seed = true }) +o.validate = function(self, value) + return api.trim(value:gsub("[\r\n]", "")) +end + +-- o = s:option(Value, "minversion", translate("minversion")) +-- o.default = "1.3" +-- o:value("1.3") +--o:depends({ tls = true }) + +-- [[ TLS ]] -- +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:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2"}) +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 = 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:depends({ tls = true, reality = false }) +o:depends({ protocol = "hysteria2"}) +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 = s:option(Flag, "ech", translate("ECH")) +o.default = "0" +o:depends({ tls = true, reality = false }) + +o = s:option(TextValue, "ech_key", translate("ECH Key")) +o.default = "" +o.rows = 5 +o.wrap = "soft" +o:depends({ ech = true }) +o.validate = function(self, value) + return api.trim(value:gsub("[\r\n]", "")) +end + +o = s:option(ListValue, "transport", translate("Transport")) +o:value("raw", "RAW") +o:value("mkcp", "mKCP") +o:value("ws", "WebSocket") +o:value("grpc", "gRPC") +o:value("httpupgrade", "HttpUpgrade") +o:value("xhttp", "XHTTP") +o:depends({ protocol = "vmess" }) +o:depends({ protocol = "vless" }) +o:depends({ protocol = "socks" }) +o:depends({ protocol = "shadowsocks" }) +o:depends({ protocol = "trojan" }) + +-- [[ WebSocket ]]-- +o = s:option(Value, "ws_host", translate("WebSocket Host")) +o:depends({ transport = "ws" }) + +o = s:option(Value, "ws_path", translate("WebSocket Path")) +o:depends({ transport = "ws" }) + +-- [[ HttpUpgrade ]]-- +o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host")) +o:depends({ transport = "httpupgrade" }) + +o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path")) +o.placeholder = "/" +o:depends({ transport = "httpupgrade" }) + +-- [[ XHTTP ]]-- +o = s:option(Value, "xhttp_host", translate("XHTTP Host")) +o:depends({ transport = "xhttp" }) + +o = s:option(Value, "xhttp_path", translate("XHTTP Path")) +o.placeholder = "/" +o:depends({ transport = "xhttp" }) + +o = s:option(Value, "xhttp_maxuploadsize", translate("maxUploadSize")) +o.default = "1000000" +o:depends({ transport = "xhttp" }) + +o = s:option(Value, "xhttp_maxconcurrentuploads", translate("maxConcurrentUploads")) +o.default = "10" +o:depends({ transport = "xhttp" }) + +o = s:option(Value, "splithttp_maxuploadsize", translate("maxUploadSize")) +o.default = "1000000" +o:depends({ transport = "splithttp" }) + +o = s:option(Value, "splithttp_maxconcurrentuploads", translate("maxConcurrentUploads")) +o.default = "10" +o:depends({ transport = "splithttp" }) + +-- [[ TCP ]]-- + +o = s:option(ListValue, "tcp_guise", translate("Camouflage Type")) +o:value("none", "none") +o:value("http", "http") +o:depends({ transport = "raw" }) + +o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host")) +o:depends({ tcp_guise = "http" }) + +o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path")) +o:depends({ tcp_guise = "http" }) + +-- [[ mKCP ]]-- + +o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.')) +for a, t in ipairs(header_type_list) do o:value(t) end +o:depends({ transport = "mkcp" }) + +o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain.")) +o:depends({ mkcp_guise = "dns" }) + +o = s:option(Value, "mkcp_mtu", translate("KCP MTU")) +o.datatype = "uinteger" +o.default = 1350 +o:depends({ transport = "mkcp" }) + +o = s:option(Value, "mkcp_seed", translate("KCP Seed")) +o:depends({ transport = "mkcp" }) + +-- [[ gRPC ]]-- +o = s:option(Value, "grpc_serviceName", "ServiceName") +o:depends({ transport = "grpc" }) + +--[[FinalMask]] +o = s:option(Flag, "use_finalmask", "FinalMask") +o.default = "0" +o:depends({ custom = false, protocol = "vmess" }) +o:depends({ custom = false, protocol = "vless" }) +o:depends({ custom = false, protocol = "trojan" }) +o:depends({ custom = false, protocol = "shadowsocks" }) +o:depends({ custom = false, protocol = "hysteria2", hysteria2_realms = false }) + +o = s:option(TextValue, "finalmask", "FinalMask JSON") +o:depends({ use_finalmask = true }) +o.rows = 10 +o.wrap = "off" +o.datatype = "json" +o.custom_cfgvalue = function(self, section, value) + local raw = m:get(section, "finalmask") + if raw then + return api.base64Decode(raw) + end +end +o.custom_write = function(self, section, value) + m:set(section, "finalmask", api.base64Encode(value) or "") +end + +--[[acceptProxyProtocol]] +o = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used.")) +o.default = "0" +o:depends({ transport = "raw" }) +o:depends({ transport = "ws" }) + +--[[Fast Open]] +o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) +o.default = "0" +o:depends({ protocol = "vmess", custom = false }) +o:depends({ protocol = "vless", custom = false }) +o:depends({ protocol = "shadowsocks", custom = false }) +o:depends({ protocol = "trojan", custom = false }) + +-- [[ Fallback ]]-- +o = s:option(Flag, "fallback", translate("Fallback")) +o:depends({ protocol = "vless", transport = "raw" }) +o:depends({ protocol = "trojan", transport = "raw" }) + +--[[ +o = s:option(Value, "fallback_alpn", "Fallback alpn") +o:depends({ fallback = true }) + +o = s:option(Value, "fallback_path", "Fallback path") +o:depends({ fallback = true }) + +o = s:option(Value, "fallback_dest", "Fallback dest") +o:depends({ fallback = true }) + +o = s:option(Value, "fallback_xver", "Fallback xver") +o.default = 0 +o:depends({ fallback = true }) +]]-- + +o = s:option(DynamicList, "fallback_list", "Fallback", translate("format: dest,path,xver")) +o:depends({ fallback = true }) + +-- Not supported at present +--[[ +o = s:option(Flag, "wireguard_system_interface", translate("System interface")) +o.default = 0 +o:depends({ protocol = "wireguard" }) +]]-- + +o = s:option(Value, "wireguard_mtu", "MTU") +o.default = "1420" +o:depends({ protocol = "wireguard" }) + +-- Not supported at present +--[[ +o = s:option(DynamicList, "wireguard_local_address", translate("Local Address")) +o:depends({ protocol = "wireguard" }) +]]-- + +o = s:option(Value, "wireguard_private_key", translate("Private Key")) +o.datatype = "base64" +o:depends({ protocol = "wireguard" }) + +o = s:option(Value, "wireguard_public_key", translate("Public Key")) +o.datatype = "base64" +o:depends({ protocol = "wireguard" }) + +o = s:option(DummyValue, "gen_wireguard_key") +o.template = m:template_path("/server/gen_wireguard_key") +o:depends({ protocol = "wireguard" }) + +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 }) + +local nodes_table = {} +for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" and e.type == type_name then + nodes_table[#nodes_table + 1] = { + id = e[".name"], + remarks = e["remark"], + group = e["group"] + } + end +end + +o = s:option(ListValue, "outbound_node", translate("outbound node")) +o:value("", translate("Close")) +o:value("_socks", translate("Custom Socks")) +o:value("_http", translate("Custom HTTP")) +o:value("_iface", translate("Custom Interface")) +o.template = m:template_path("/cbi/nodes_listvalue") +o.group = {"","","",""} +for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") +end +o:depends({ custom = false }) + +o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)")) +o:depends({ outbound_node = "_socks"}) +o:depends({ outbound_node = "_http"}) + +o = s:option(Value, "outbound_node_port", translate("Port")) +o.datatype = "port" +o:depends({ outbound_node = "_socks"}) +o:depends({ outbound_node = "_http"}) + +o = s:option(Value, "outbound_node_username", translate("Username")) +o:depends({ outbound_node = "_socks"}) +o:depends({ outbound_node = "_http"}) + +o = s:option(Value, "outbound_node_password", translate("Password")) +o.password = true +o:depends({ outbound_node = "_socks"}) +o:depends({ outbound_node = "_http"}) + +o = s:option(Value, "outbound_node_iface", translate("Interface")) +o:depends({ outbound_node = "_iface"}) +local netdev_list = api.get_network_devices() +for _, d in ipairs(netdev_list) do + o:value(d.name, d.label) +end + +o = s:option(Flag, "log", translate("Log")) +o.default = "1" +o.rmempty = false + +o = s:option(ListValue, "loglevel", translate("Log Level")) +o.default = "warning" +o:value("debug") +o:value("info") +o:value("warning") +o:value("error") +o:depends({ log = true }) + api.luci_types(s1, s) \ No newline at end of file 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 df31354e..82d6acac 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 @@ -1,90 +1,90 @@ -if not api.is_finded("ssserver") then - return -end - --- [[ Shadowsocks Rust ]] -local m, s1 = ... -local type_name = "SS-Rust" - -s1.fields["type"]:value(type_name, translate("Shadowsocks Rust")) - -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 = "ssrust_" - -local ssrust_encrypt_method_list = { - "plain", "none", - "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", - "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" -} - -o = s:option(Flag, "custom", translate("Use Custom Config")) - -o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") -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(ListValue, "user", translate("User")) -for i, v in ipairs(user_list) do - o:value(v[".name"], v.username) -end -o:depends({ custom = false }) - -o = s:option(ListValue, "method", translate("Encrypt Method")) -for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end -o:depends({ custom = false }) - -o = s:option(Value, "timeout", translate("Connection Timeout")) -o.datatype = "uinteger" -o.default = 300 -o:depends({ custom = false }) - -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 - +if not api.is_finded("ssserver") then + return +end + +-- [[ Shadowsocks Rust ]] +local m, s1 = ... +local type_name = "SS-Rust" + +s1.fields["type"]:value(type_name, translate("Shadowsocks Rust")) + +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 = "ssrust_" + +local ssrust_encrypt_method_list = { + "plain", "none", + "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" +} + +o = s:option(Flag, "custom", translate("Use Custom Config")) + +o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") +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(ListValue, "user", translate("User")) +for i, v in ipairs(user_list) do + o:value(v[".name"], v.username) +end +o:depends({ custom = false }) + +o = s:option(ListValue, "method", translate("Encrypt Method")) +for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end +o:depends({ custom = false }) + +o = s:option(Value, "timeout", translate("Connection Timeout")) +o.datatype = "uinteger" +o.default = 300 +o:depends({ custom = false }) + +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 + api.luci_types(s1, s) \ No newline at end of file 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 3e74f960..12da8994 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 @@ -1,121 +1,121 @@ -if not api.is_finded("ssr-server") then - return -end - --- [[ ShadowsocksR ]] -local m, s1 = ... -local type_name = "SSR" - -s1.fields["type"]:value(type_name, translate("ShadowsocksR")) - -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 = "ssr_" - -local ssr_encrypt_method_list = { - "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", - "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", - "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", - "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", - "chacha20-ietf" -} - -local ssr_protocol_list = { - "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", - "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", - "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", - "auth_chain_d", "auth_chain_e", "auth_chain_f" -} -local ssr_obfs_list = { - "plain", "http_simple", "http_post", "random_head", "tls_simple", - "tls1.0_session_auth", "tls1.2_ticket_auth" -} - -o = s:option(Flag, "custom", translate("Use Custom Config")) - -o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") -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(ListValue, "user", translate("User")) -for i, v in ipairs(user_list) do - o:value(v[".name"], v.username) -end -o:depends({ custom = false }) - -o = s:option(ListValue, "method", translate("Encrypt Method")) -for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end -o:depends({ custom = false }) - -o = s:option(ListValue, "protocol", translate("Protocol")) -for a, t in ipairs(ssr_protocol_list) do o:value(t) end -o:depends({ custom = false }) - -o = s:option(Value, "protocol_param", translate("Protocol_param")) -o:depends({ custom = false }) - -o = s:option(ListValue, "obfs", translate("Obfs")) -for a, t in ipairs(ssr_obfs_list) do o:value(t) end -o:depends({ custom = false }) - -o = s:option(Value, "obfs_param", translate("Obfs_param")) -o:depends({ custom = false }) - -o = s:option(Value, "timeout", translate("Connection Timeout")) -o.datatype = "uinteger" -o.default = 300 -o:depends({ custom = false }) - -o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) -o.default = "0" -o:depends({ custom = false }) - -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 - +if not api.is_finded("ssr-server") then + return +end + +-- [[ ShadowsocksR ]] +local m, s1 = ... +local type_name = "SSR" + +s1.fields["type"]:value(type_name, translate("ShadowsocksR")) + +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 = "ssr_" + +local ssr_encrypt_method_list = { + "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", + "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", + "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", + "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", + "chacha20-ietf" +} + +local ssr_protocol_list = { + "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", + "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", + "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", + "auth_chain_d", "auth_chain_e", "auth_chain_f" +} +local ssr_obfs_list = { + "plain", "http_simple", "http_post", "random_head", "tls_simple", + "tls1.0_session_auth", "tls1.2_ticket_auth" +} + +o = s:option(Flag, "custom", translate("Use Custom Config")) + +o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)") +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(ListValue, "user", translate("User")) +for i, v in ipairs(user_list) do + o:value(v[".name"], v.username) +end +o:depends({ custom = false }) + +o = s:option(ListValue, "method", translate("Encrypt Method")) +for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end +o:depends({ custom = false }) + +o = s:option(ListValue, "protocol", translate("Protocol")) +for a, t in ipairs(ssr_protocol_list) do o:value(t) end +o:depends({ custom = false }) + +o = s:option(Value, "protocol_param", translate("Protocol_param")) +o:depends({ custom = false }) + +o = s:option(ListValue, "obfs", translate("Obfs")) +for a, t in ipairs(ssr_obfs_list) do o:value(t) end +o:depends({ custom = false }) + +o = s:option(Value, "obfs_param", translate("Obfs_param")) +o:depends({ custom = false }) + +o = s:option(Value, "timeout", translate("Connection Timeout")) +o.datatype = "uinteger" +o.default = 300 +o:depends({ custom = false }) + +o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open")) +o.default = "0" +o:depends({ custom = false }) + +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 + 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 491d402f..0bc1ab4d 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -464,14 +464,14 @@ datatypes.json = is_json function is_timehhmm(str) local hour, minute = string.match(str, "^(%d?%d):(%d%d)$") - if hour and minute then - hour = tonumber(hour) - minute = tonumber(minute) - if hour >= 0 and hour <= 23 and minute >= 0 and minute <= 59 then - return true - end - end - return false + if hour and minute then + hour = tonumber(hour) + minute = tonumber(minute) + if hour >= 0 and hour <= 23 and minute >= 0 and minute <= 59 then + return true + end + end + return false end datatypes.timehhmm = is_timehhmm diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index a5312e68..eb65f6da 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -922,12 +922,12 @@ function gen_config(var) if xray_settings.fragment == "1" then local lengths, delays = {}, {} api.trim(xray_settings.fragment_lengths):gsub("[^,]+", function(w) - w = w:gsub("%s+", "") - if w ~= "" then lengths[#lengths+1] = w end + w = w:gsub("%s+", "") + if w ~= "" then lengths[#lengths+1] = w end end) api.trim(xray_settings.fragment_delays):gsub("[^,]+", function(w) - w = w:gsub("%s+", "") - if w ~= "" then delays[#delays+1] = w end + w = w:gsub("%s+", "") + if w ~= "" then delays[#delays+1] = w end end) fragment_table = { type = "fragment", diff --git a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm index d470ea4b..3aa8f39f 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/cbi/nodes_value_com.htm @@ -534,7 +534,7 @@ //自定义框 function v_customEnter(cbid, labelSpan, hiddenInput, searchInput, panel, listContainer, customInput) { let inputValue = customInput.value.trim(); - if (!inputValue) { + if (!inputValue) { return; } const existingItems = listContainer.querySelectorAll('li[data-key="' + inputValue + '"]'); diff --git a/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm b/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm index db4d1e31..60044c8a 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/global/faq.htm @@ -3,28 +3,28 @@ local map = self.map local api = map.api -%>