✨ Sync 2026-08-13 21:00:03

This commit is contained in:
github-actions[bot] committed 2026-08-13 21:00:03 +08:00
1 parent 08ad2012b5
commit a5a8d2e582
48 files changed
+1339 -1967

No files matched your search

+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.08.05
PKG_RELEASE:=34
PKG_VERSION:=2026.08.13
PKG_RELEASE:=35
PKG_SOURCE:=dae-src-2026.08.05-fc1a5bf558fd.tar.gz
PKG_SOURCE:=dae-src-2026.08.13-44554e556f44.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.08.05
PKG_RELEASE:=45
PKG_VERSION:=2026.08.13
PKG_RELEASE:=46
PKG_SOURCE:=daed-src-2026.08.05-1d87c2949acc.tar.gz
PKG_SOURCE:=daed-src-2026.08.13-b5028aedb364.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
@@ -211,7 +211,7 @@ function parseShareLink(uri, features) {
case 'socks':
case 'socks4':
case 'socks4a':
case 'socsk5':
case 'socks5':
case 'socks5h':
url = new URL('http://' + uri[1]);
@@ -52,8 +52,7 @@
8.150.64.0/23
8.152.0.0/13
8.160.0.0/15
8.162.0.0/18
8.162.64.0/19
8.162.0.0/17
8.163.0.0/16
8.164.0.0/14
8.168.0.0/15
@@ -67,7 +66,6 @@
14.144.0.0/12
14.204.0.0/15
14.208.0.0/12
14.241.232.0/21
14.255.16.0/24
14.255.238.0/24
14.255.254.0/24
@@ -312,6 +310,7 @@
43.248.108.0/22
43.248.112.0/21
43.248.128.0/20
43.248.184.0/21
43.248.192.0/20
43.248.232.0/22
43.248.244.0/22
@@ -485,7 +484,7 @@
52.82.134.0/23
52.82.136.0/24
52.82.144.0/23
52.82.148.0/22
52.82.148.0/23
52.82.160.0/21
52.82.170.0/23
52.82.172.0/22
@@ -562,7 +561,7 @@
59.111.18.0/23
59.111.21.0/24
59.111.22.0/24
59.111.24.0/21
59.111.24.0/22
59.111.32.0/24
59.111.35.0/24
59.111.36.0/24
@@ -577,7 +576,9 @@
59.111.128.0/20
59.111.144.0/24
59.111.152.0/21
59.111.160.0/21
59.111.160.0/23
59.111.162.0/24
59.111.164.0/22
59.111.168.0/22
59.111.173.0/24
59.111.175.0/24
@@ -623,18 +624,12 @@
60.253.128.0/17
60.255.0.0/16
61.4.80.0/23
61.10.96.0/20
61.10.128.0/20
61.10.160.0/19
61.15.0.0/18
61.18.0.0/18
61.18.64.0/19
61.28.20.0/24
61.28.22.0/24
61.28.49.0/24
61.28.108.0/24
61.28.112.0/20
61.29.128.0/18
61.29.161.0/24
61.47.128.0/18
61.48.0.0/13
61.87.192.0/18
@@ -731,6 +726,7 @@
101.124.0.0/20
101.124.19.0/24
101.124.22.0/24
101.124.24.0/21
101.124.62.0/24
101.125.0.0/22
101.125.4.0/23
@@ -775,10 +771,9 @@
101.237.128.0/20
101.240.0.0/14
101.245.0.0/17
101.245.128.0/20
101.245.144.0/22
101.245.152.0/21
101.245.128.0/19
101.245.160.0/20
101.245.176.0/22
101.246.172.0/22
101.246.176.0/20
101.248.0.0/15
@@ -793,6 +788,8 @@
101.254.32.0/19
101.254.64.0/18
101.254.128.0/17
102.141.200.0/24
102.220.90.0/24
103.1.8.0/22
103.1.20.0/22
103.1.168.0/22
@@ -1151,6 +1148,7 @@
103.144.66.0/23
103.144.70.0/24
103.144.158.0/23
103.144.244.0/24
103.145.42.0/23
103.146.126.0/23
103.147.124.0/24
@@ -1188,7 +1186,6 @@
103.170.4.0/23
103.170.212.0/23
103.174.94.0/23
103.177.28.0/23
103.179.78.0/23
103.181.234.0/24
103.183.66.0/23
@@ -1222,6 +1219,8 @@
103.200.144.0/21
103.200.220.0/22
103.200.224.0/24
103.200.228.0/24
103.200.232.0/24
103.201.192.0/24
103.201.196.0/24
103.201.200.0/24
@@ -1255,6 +1254,7 @@
103.212.4.0/22
103.212.12.0/22
103.212.48.0/23
103.212.50.0/24
103.212.109.0/24
103.213.49.0/24
103.213.50.0/23
@@ -1343,6 +1343,7 @@
103.236.56.0/21
103.236.64.0/19
103.236.96.0/22
103.236.116.0/23
103.236.120.0/22
103.236.244.0/22
103.236.248.0/21
@@ -1379,6 +1380,7 @@
103.240.124.0/22
103.240.244.0/22
103.241.95.0/24
103.241.220.0/22
103.242.168.0/23
103.242.172.0/22
103.242.212.0/22
@@ -1409,12 +1411,11 @@
103.251.96.0/22
103.251.124.0/22
103.251.160.0/22
103.251.204.0/24
103.251.204.0/23
103.251.207.0/24
103.251.240.0/22
103.252.36.0/24
103.252.172.0/22
103.252.248.0/22
103.253.60.0/22
103.253.204.0/22
103.254.68.0/22
@@ -1425,6 +1426,7 @@
103.255.92.0/22
103.255.140.0/22
103.255.200.0/22
104.251.176.0/22
106.0.4.0/22
106.2.37.0/24
106.2.40.0/23
@@ -1445,7 +1447,6 @@
106.2.100.0/22
106.2.104.0/21
106.2.112.0/21
106.2.124.0/22
106.2.128.0/19
106.3.0.0/19
106.3.32.0/20
@@ -1655,7 +1656,6 @@
113.130.112.0/21
113.132.0.0/14
113.136.0.0/13
113.164.25.0/24
113.194.0.0/15
113.197.101.0/24
113.197.103.0/24
@@ -1895,7 +1895,9 @@
116.198.160.0/20
116.198.176.0/21
116.198.192.0/18
116.199.0.0/17
116.199.0.0/18
116.199.66.0/23
116.199.110.0/23
116.204.0.0/17
116.205.0.0/16
116.207.0.0/16
@@ -2063,7 +2065,10 @@
118.193.96.0/19
118.194.32.0/19
118.194.134.0/23
118.194.240.0/21
118.194.240.0/24
118.194.242.0/23
118.194.244.0/24
118.194.246.0/23
118.195.128.0/17
118.196.0.0/16
118.197.0.0/18
@@ -2095,6 +2100,7 @@
119.18.208.0/21
119.18.224.0/24
119.18.226.0/24
119.18.234.0/24
119.18.237.0/24
119.18.239.0/24
119.23.0.0/16
@@ -2102,8 +2108,8 @@
119.28.28.0/24
119.29.0.0/16
119.31.192.0/19
119.32.0.0/15
119.34.0.0/16
119.32.0.0/18
119.33.194.0/23
119.36.0.0/16
119.37.0.0/23
119.37.2.0/24
@@ -2127,8 +2133,7 @@
119.44.16.0/22
119.44.20.0/23
119.44.23.0/24
119.44.24.0/22
119.44.28.0/23
119.44.24.0/23
119.44.45.0/24
119.44.46.0/24
119.44.50.0/23
@@ -2137,8 +2142,6 @@
119.44.192.0/24
119.44.200.0/24
119.44.205.0/24
119.44.208.0/22
119.44.212.0/23
119.44.214.0/24
119.44.217.0/24
119.44.218.0/23
@@ -2488,7 +2491,7 @@
123.254.96.0/21
124.14.5.0/24
124.14.8.0/24
124.14.12.0/24
124.14.12.0/23
124.14.15.0/24
124.14.22.0/24
124.14.64.0/18
@@ -2558,7 +2561,6 @@
125.98.128.0/17
125.104.0.0/13
125.112.0.0/12
125.171.0.0/16
125.208.0.0/19
125.208.32.0/20
125.208.49.0/24
@@ -2736,7 +2738,6 @@
156.227.239.0/24
156.232.9.0/24
156.232.10.0/23
156.242.5.0/24
157.0.0.0/16
157.10.35.0/24
157.10.112.0/23
@@ -2793,6 +2794,7 @@
163.61.202.0/23
163.61.214.0/23
163.125.0.0/16
163.128.254.0/23
163.142.0.0/16
163.177.0.0/16
163.179.0.0/16
@@ -2802,6 +2804,11 @@
163.244.246.0/24
165.101.70.0/24
165.101.208.0/23
166.0.97.0/24
166.0.100.0/23
166.1.139.0/24
166.1.218.0/24
166.1.232.0/24
166.111.0.0/16
167.139.0.0/16
167.220.244.0/22
@@ -2944,6 +2951,7 @@
183.78.180.0/22
183.81.180.0/22
183.84.0.0/15
183.91.37.0/24
183.91.39.0/24
183.91.40.0/21
183.91.48.0/21
@@ -3017,9 +3025,10 @@
202.47.104.0/21
202.51.32.0/20
202.55.0.0/19
202.57.192.0/24
202.57.192.0/23
202.57.196.0/22
202.57.200.0/23
202.57.202.0/24
202.57.204.0/23
202.59.213.0/24
202.59.214.0/23
@@ -3135,8 +3144,10 @@
203.8.30.0/24
203.12.91.0/24
203.12.93.0/24
203.12.95.0/24
203.13.81.0/24
203.15.0.0/20
203.15.114.0/24
203.25.52.0/24
203.25.208.0/20
203.32.48.0/23
@@ -3303,6 +3314,7 @@
203.95.2.0/24
203.95.4.0/22
203.95.128.0/23
203.99.18.0/24
203.99.21.0/24
203.99.22.0/23
203.99.26.0/23
@@ -3321,7 +3333,7 @@
203.107.72.0/21
203.107.80.0/21
203.107.96.0/23
203.107.116.0/22
203.107.112.0/21
203.107.120.0/22
203.107.124.0/23
203.110.160.0/19
@@ -3393,8 +3405,8 @@
203.195.128.0/17
203.196.0.0/21
203.202.236.0/23
203.205.88.0/23
203.205.90.0/24
203.205.88.0/22
203.205.95.0/24
203.207.64.0/19
203.207.96.0/21
203.207.104.0/22
@@ -3506,8 +3518,7 @@
210.79.224.0/19
210.82.0.0/15
210.87.128.0/18
210.185.192.0/18
210.192.96.0/20
210.192.96.0/21
210.192.116.0/22
210.192.120.0/21
211.64.0.0/13
@@ -3611,7 +3622,8 @@
211.147.208.0/20
211.147.224.0/19
211.148.0.0/18
211.148.128.0/17
211.148.128.0/18
211.148.192.0/19
211.149.128.0/17
211.150.0.0/18
211.150.64.0/19
@@ -3644,7 +3656,6 @@
211.155.240.0/20
211.156.0.0/19
211.156.128.0/19
211.156.176.0/20
211.156.192.0/20
211.156.208.0/21
211.156.216.0/22
@@ -3785,8 +3796,6 @@
218.247.64.0/19
218.247.128.0/17
218.249.0.0/16
218.252.0.0/17
218.253.0.0/18
219.72.0.0/16
219.82.0.0/16
219.83.160.0/19
@@ -3823,6 +3832,7 @@
219.235.32.0/19
219.235.64.0/18
219.235.128.0/19
219.235.164.0/23
219.235.192.0/23
219.235.207.0/24
219.235.224.0/22
@@ -3982,9 +3992,6 @@
222.126.240.0/20
222.128.0.0/12
222.160.0.0/14
222.166.96.0/20
222.166.160.0/20
222.166.240.0/20
222.168.0.0/13
222.176.0.0/12
222.192.0.0/11
@@ -1 +1 @@
20260704060757
20260811040949
@@ -8,6 +8,7 @@
2001:daa:2::/47
2001:daa:4::/47
2001:daa:6::/48
2001:daa:9::/48
2001:dc7::/32
2001:dd8:1::/48
2001:dd9::/48
@@ -57,12 +58,14 @@
2400:9380:90b1::/48
2400:9380:90b2::/47
2400:9380:90b4::/46
2400:9380:9109::/48
2400:9380:9121::/48
2400:9380:9220::/47
2400:9380:9250::/47
2400:9380:9260::/48
2400:9380:9271::/48
2400:9380:9280::/47
2400:9380:9282::/48
2400:9380:92b1::/48
2400:9380:92b2::/47
2400:9380:92b4::/46
@@ -131,6 +134,7 @@
2401:fa00:40::/48
2402:840:d000::/46
2402:840:e000::/46
2402:840:f000::/38
2402:c60::/48
2402:1440::/32
2402:2000::/32
@@ -237,6 +241,8 @@
2404:7600::/32
2404:7940::/32
2404:c2c0::/40
2404:c2c0:111::/48
2404:c2c0:112::/48
2404:c2c0:240::/44
2404:c2c0:280::/44
2404:c2c0:2c0::/44
@@ -278,7 +284,6 @@
2405:e000::/32
2405:f940::/32
2406:280::/32
2406:840:9680::/44
2406:840:f889::/48
2406:1080::/48
2406:2700::/32
@@ -463,7 +468,7 @@
2408:8406:a980::/42
2408:8406:b500::/41
2408:8406:b580::/42
2408:8407:500::/42
2408:8407:520::/43
2408:8409::/40
2408:8409:100::/41
2408:8409:1a0::/43
@@ -807,6 +812,7 @@
2408:882c::/32
2408:883a::/32
2408:8844::/43
2408:8844:40::/43
2408:8856::/31
2408:8858::/30
2408:8862::/31
@@ -983,26 +989,25 @@
2a04:f580:9270::/48
2a04:f580:9280::/48
2a04:f580:9290::/48
2a05:1086::/32
2a0a:d685:1ff::/48
2a0a:d685:300::/40
2a0b:4340:90::/48
2a0b:4340:97::/48
2a0d:c7c7:400::/38
2a0e:aa06:49f::/48
2a0e:b107:740::/44
2a0e:b107:2440::/44
2a0f:1cc5:1600::/44
2a0f:1cc5:4700::/40
2a0f:9400:6110::/48
2a12:cb41:600::/44
2a12:cb41:1200::/44
2a12:cb41:1300::/43
2a12:cb41:1300::/44
2a12:cb41:13f0::/44
2a13:1801:180::/43
2a13:a5c3:f000::/40
2a14:7580:d00::/43
2a14:7580:d10::/44
2a14:7580:d20::/46
2a14:7580:d41::/48
2a14:7580:d42::/47
2a14:7580:dff::/48
2a14:7583:efe7::/48
2c0f:f7a8:8011::/48
2c0f:f7a8:8050::/48
2c0f:f7a8:805f::/48
@@ -1 +1 @@
20260704060757
20260811040949
File diff suppressed because it is too large. Load diff
@@ -1 +1 @@
202607052256
202608102230
@@ -13,6 +13,7 @@
173ng.com
18board.com
18comic.org
18comic.vip
18mh.net
1984bbs.com
1991way.com
@@ -127,6 +128,7 @@ agoogleaday.com
agro.hk
ai.dev
ai.studio
aihub.top
aiosearch.com
aiph.net
airconsole.com
@@ -294,7 +296,6 @@ atlanta168.com
atnext.com
auctions.yahoo.co.jp
audacy.com
auntology.fandom.com
autodraw.com
av.movie
av01.tv
@@ -342,6 +343,7 @@ baramangaonline.com
barnabu.co.uk
bartender.dowjones.com
barton.de
basemaps.arcgis.com
bastillepost.com
bayvoice.net
bb-chat.tv
@@ -686,6 +688,7 @@ centauro.com.br
centerforhumanreprod.com
centralnation.com
centurys.net
cerebras.ai
certificate-transparency.org
certificate.revocationcheck.com
cfr.org
@@ -766,10 +769,12 @@ chrome.com
chromecast.com
chromeexperiments.com
chromestatus.com
cht.com.tw
ci-en.jp
cia.gov
cici.com
ciciai.com
circle19.org
cirosantilli.com
citizencn.com
citizenlab.ca
@@ -782,6 +787,7 @@ civilmedia.tw
civitai.com
cixiaoya.club
ck101.com
clashyun.com
classicalguitarblog.net
claude.ai
claude.com
@@ -991,6 +997,7 @@ disneyplus.com
disp.cc
disqus.com
dit-inc.us
divdyn.com
diyin.org
dizhuzhishang.com
dl-laby.jp
@@ -1140,6 +1147,7 @@ eromanga-kingdom.com
eromangadouzin.com
eromon.net
esg.t91y.com
esim.redteago.com
eslite.com
esurance.com
etherdelta.com
@@ -1162,6 +1170,7 @@ exmormon.org
expatshield.com
expecthim.com
expekt.com
experience.arcgis.com
exploader.net
expressnews.com
expressvpn.com
@@ -1201,6 +1210,7 @@ falundafaindia.org
falunhr.org
famunion.com
fanbox.cc
fandom.com
fangeqiang.com
fanglizhi.info
fangong.org
@@ -1892,7 +1902,6 @@ holyspiritspeaks.org
home.saxo
homedepot.com
homeservershow.com
hongkong.fandom.com
hongkongfp.com
hongzhi.li
honven.xyz
@@ -2171,6 +2180,7 @@ kanshifang.com
kantie.org
kaotic.com
karayou.com
kards.com
kathmandupost.com
kawaiikawaii.jp
kawase.com
@@ -2255,6 +2265,7 @@ lenwhite.com
leonardo.ai
lesoir.be
letscorp.net
level-plus.net
lexica.art
lhakar.org
li.taipei
@@ -2518,7 +2529,6 @@ mosavi.io
motherless.com
movements.org
moviefap.com
mozilla.ai
mpettis.com
mpfinance.com
mpinews.com
@@ -2673,6 +2683,7 @@ ntd.tv
ntdtv.com
ntdtv.com.tw
ntdtv.cz
nthlink.com
nuexpo.com
nurgo-software.com
nusatrip.com
@@ -2987,7 +2998,6 @@ puffinbrowser.com
pullfolio.co
pure18.com
pureconcepts.net
puredns.org
pureinsight.org
purepdf.com
purevpn.com
@@ -3032,7 +3042,6 @@ radio.co
radio.garden
radioaustralia.net.au
radiohilight.net
radiojar.com
radioline.co
radiovaticana.org
radiovncr.com
@@ -3145,7 +3154,6 @@ sadpanda.us
safechat.com
safeguarddefenders.com
safervpn.com
sagernet.org
saintyculture.com
sakuralive.com
salvation.org.hk
@@ -3435,6 +3443,7 @@ synapse.org
syncback.com
synergyse.com
syosetu.com
sysub.com
t-g.com
t.co
t.me
@@ -3648,6 +3657,7 @@ to-porno.com
togetter.com
token.im
tokenlon.im
tokenplus.app
tokyo-porn-tube.com
tokyocn.com
tomp3.cc
@@ -3689,6 +3699,7 @@ trtworld.com
truebuddha-md.org
trustwallet.com
truthsocial.com
tryai.dev
tryheart.jp
tsdr.uspto.gov
tt1069.com
@@ -3870,6 +3881,7 @@ uyghurstudies.org
uyghurtribunal.com
uygur.fc2web.com
uymaarip.com
v.recipes
v2.help
v2.hysteria.network
v2ex.com
@@ -4135,7 +4147,6 @@ www.ajsands.com
www.antd.org
www.aolnews.com
www.bing.com
www.clashverge.dev
www.cmoinc.org
www.dmm.com
www.eastturkistan.net
@@ -4313,6 +4324,7 @@ zdnet.com.tw
zello.com
zenmate.com
zenmate.com.ru
zenmux.ai
zerohedge.com
zeronet.io
zh-hans.cfsh99.com
@@ -1 +1 @@
202607052256
202608102230
@@ -13,7 +13,6 @@ import { connect } from 'ubus';
import { cursor } from 'uci';
import { urldecode, urlencode } from 'luci.http';
import { init_action } from 'luci.sys';
import { remove_subscription_nodes } from '/etc/homeproxy/scripts/node_references.uc';
import { parse_surge_subscription } from '/etc/homeproxy/scripts/subscription_parsers.uc';
@@ -216,6 +215,10 @@ function apply_tls_cert_pin_policy(config, label, pin) {
version));
}
function service_action(action) {
return system([ '/etc/init.d/homeproxy', action ]);
}
function parse_uri(uri) {
let config, url, params;
@@ -857,8 +860,8 @@ function main() {
if (need_restart) {
log('Restarting service...');
init_action('homeproxy', 'stop');
init_action('homeproxy', 'start');
service_action('stop');
service_action('start');
}
log(sprintf('%s nodes added, %s updated, %s removed.', added, updated, removed));
@@ -885,8 +888,8 @@ if (!isEmpty(subscription_urls)) {
'请查看 HomeProxy 日志中的 [SUBSCRIBE] 记录并修复订阅配置');
log('Restarting service...');
init_action('homeproxy', 'stop');
init_action('homeproxy', 'start');
service_action('stop');
service_action('start');
writeSubscriptionUpdateStatus(false, true, false, status_error);
}
} else {
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.8.12
PKG_RELEASE:=215
PKG_RELEASE:=216
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -20,19 +20,31 @@ function is_timehhmm(str) {
}
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
cbi_validators['base64'] = function() {
return isBase64(this);
}
cbi_validators['json'] = function() {
return is_json(this);
}
cbi_validators['timehhmm'] = function() {
return is_timehhmm(this);
}
cbi_validators['uuid'] = function() {
return isUUID(this);
}
} else {
L.require('validation').then(function(validation) {
validation.types['base64'] = function() {
return this.assert(isBase64(this.value), _('Must be Base64 text!'));
}
validation.types['json'] = function() {
return this.assert(is_json(this.value), _('Must be JSON text!'));
}
validation.types['timehhmm'] = function() {
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
}
validation.types['uuid'] = function() {
return this.assert(isUUID(this.value), _('Must be UUID format!'));
}
});
}
@@ -8,6 +8,11 @@ function arraysEqual(a, b) {
return true;
}
function isBase64(str) {
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
return base64Regex.test(str);
}
function decodeIfBase64(str) {
try {
let s = str.replace(/-/g, '+').replace(/_/g, '/');
@@ -24,6 +29,11 @@ function decodeIfBase64(str) {
return str;
}
function isUUID(str) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(str);
}
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) return callback(el);
@@ -57,12 +57,13 @@ function index()
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
entry({"admin", "services", appname, "server_config"}, cbi(appname .. "/server/server_config")).leaf = true
entry({"admin", "services", appname, "server_user_config"}, cbi(appname .. "/server/user_config")).leaf = true
--[[ API ]]
entry({"admin", "services", appname, "server_update_config"}, call("server_update_config")).leaf = true
entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
entry({"admin", "services", appname, "server_status"}, call("server_status")).leaf = true
entry({"admin", "services", appname, "server_log"}, call("server_log")).leaf = true
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
@@ -89,7 +90,6 @@ function index()
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "reassign_group"}, call("reassign_group")).leaf = true
entry({"admin", "services", appname, "get_node"}, call("get_node")).leaf = true
entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true
entry({"admin", "services", appname, "save_node_list_opt"}, call("save_node_list_opt")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "rollback_rules"}, call("rollback_rules")).leaf = true
@@ -101,7 +101,6 @@ function index()
entry({"admin", "services", appname, "get_shunt_rules"}, call("get_shunt_rules")).leaf = true
entry({"admin", "services", appname, "add_shunt_rule"}, call("add_shunt_rule")).leaf = true
entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true
entry({"admin", "services", appname, "save_shunt_rule_order"}, call("save_shunt_rule_order")).leaf = true
--[[rule_list]]
entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true
@@ -797,19 +796,6 @@ function get_node()
http_write_json(result)
end
function save_node_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
function reassign_group()
local ids = http.formvalue("ids") or ""
local group = http.formvalue("group") or "default"
@@ -878,14 +864,14 @@ function server_update_config()
http_write_json_error()
end
function server_user_status()
function server_status()
local e = {}
e.index = http.formvalue("index")
e.status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", http.formvalue("id"))) == 0
http_write_json(e)
end
function server_user_log()
function server_log()
local id = http.formvalue("id")
if fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall_server/" .. id .. ".log")
@@ -1286,16 +1272,3 @@ function delete_select_shunt_rules()
api.uci_save(uci, appname, true, true)
end
end
function save_shunt_rule_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
@@ -1,7 +1,5 @@
local api = require "luci.passwall.api"
local appname = "passwall"
local sys = api.sys
local datatypes = api.datatypes
api.set_default_cbi()
@@ -30,6 +28,29 @@ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
m:append(Template(appname .. "/node_list/node_list"))
if true then
local o = Template(appname .. "/node_list/node_list")
o.map = m
o.api = api
m:append(o)
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(appname, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -146,7 +146,26 @@ end
s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then
m:append(Template(appname .. "/rule/shunt_rule_list"))
local o = Template(appname .. "/rule/shunt_rule_list")
o.api = api
m:append(o)
if luci.http.formvalue("cbi.submit") == "1" then
local 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(appname, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -18,38 +18,39 @@ e.default = "90"
e.datatype = "range(0,600)"
e.rmempty = false
local cfgname = "user"
t = m:section(TypedSection, cfgname, translate("Users Manager"))
t.anonymous = true
t.addremove = true
t.sortable = true
t.template = "cbi/tblsection"
t.extedit = api.url("server_user", "%s")
function t.create(e, t)
s_server = m:section(TypedSection, "server", translate("Servers Manager"))
s_server.anonymous = true
s_server.addremove = true
s_server.sortable = true
s_server.template = "cbi/tblsection"
s_server.extedit = api.url("server_config", "%s")
function s_server.create(self, section)
local uid = api.gen_random_char()
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
TypedSection.create(self, uid)
luci.http.redirect(self.extedit:format(uid))
end
function t.remove(e, t)
e.map.proceed = true
e.map:del(t)
luci.http.redirect(api.url("server"))
function s_server.remove(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m:del(section)
end
end
e = t:option(Flag, "enable", translate("Enable"))
e = s_server:option(Flag, "enable", translate("Enable"))
e.width = "5%"
e.rmempty = false
e = t:option(DummyValue, "status", translate("Status"))
e = s_server:option(DummyValue, "status", translate("Status"))
e.rawhtml = true
e.cfgvalue = function(t, n)
return string.format('<font class="_users_status">%s</font>', translate("Collecting data..."))
return string.format('<font class="_servers_status">%s</font>', translate("Collecting data..."))
end
e = t:option(DummyValue, "remarks", translate("Remarks"))
e = s_server:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%"
e = t:option(DummyValue, "type", translate("Type"))
e = s_server:option(DummyValue, "type", translate("Type"))
e.width = "20%"
e.rawhtml = true
e.cfgvalue = function(t, n)
@@ -87,20 +88,74 @@ e.cfgvalue = function(t, n)
return str
end
e = t:option(DummyValue, "port", translate("Port"))
e = s_server:option(DummyValue, "port", translate("Port"))
e = t:option(Flag, "log", translate("Log"))
e = s_server:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.appname = m.config
sortable.target_cfgname = cfgname
sortable.target_cfgname = s_server.sectiontype
m:append(sortable)
local server_list_status = Template(appname .. "/server/server_list_status")
server_list_status.api = api
server_list_status.appname = m.config
server_list_status.sectiontype = s_server.sectiontype
m:append(server_list_status)
s_user = m:section(TypedSection, "user", translate("Users Manager"))
s_user.anonymous = true
s_user.addremove = true
s_user.sortable = true
s_user.template = "cbi/tblsection"
s_user.extedit = api.url("server_user_config", "%s")
s_user.create = function(e, section)
local uid = api.gen_random_char()
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
end
s_user.remove = function(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m.uci:foreach(m.config, "server", function(o)
if o.user and section == o.user then
m:set(o[".name"], "user", "")
end
local changed = false
local users = o.users or {}
for i = #users, 1, -1 do
if section == users[i] then
table.remove(users, i)
changed = true
end
end
if changed then
m:set(o[".name"], "users", users)
end
end)
m:del(section)
luci.http.redirect(api.url("server"))
end
end
e = s_user:option(DummyValue, "username", translate("Username"))
e.width = "25%"
e = s_user:option(DummyValue, "password", translate("Password"))
e.width = "25%"
e = s_user:option(DummyValue, "uuid", "UUID")
e.width = "25%"
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.appname = m.config
sortable.target_cfgname = s_user.sectiontype
m:append(sortable)
m:append(Template(appname .. "/server/log"))
m:append(Template(appname .. "/server/users_list_status"))
return api.return_map(m)
@@ -20,7 +20,12 @@ m:append(header)
m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
s = m:section(NamedSection, arg[1], "user", "")
user_list = {}
m.uci:foreach(m.config, "user", function(s)
user_list[#user_list + 1] = s
end)
s = m:section(NamedSection, arg[1], "server", "")
s.addremove = false
s.dynamic = false
@@ -28,6 +28,12 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("realms"), translate("Realms"))
o.default = "0"
o.rewrite_option = o.option
@@ -48,11 +54,6 @@ o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio
o.rewrite_option = o.option
o:depends({ [_n("realms")] = "1" })
o = s:option(Value, _n("auth_password"), translate("Auth Password"))
o.password = true
o.rewrite_option = o.option
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -48,27 +48,18 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" })
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
o:value("tcp", "TCP")
@@ -97,6 +88,9 @@ o.rewrite_option = "method"
for a, t in ipairs(x_ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(Value, _n("ss_password"), translate("Password"))
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(ListValue, _n("ss_network"), translate("Transport"))
o.default = "tcp,udp"
o:value("tcp", "TCP")
@@ -109,14 +103,6 @@ o.default = "1"
o.rmempty = false
o:depends({ [_n("protocol")] = "socks" })
o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
o:value(api.gen_uuid())
end
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o = s:option(ListValue, _n("flow"), translate("flow"))
o.default = ""
o:value("", translate("Disable"))
@@ -141,10 +127,6 @@ o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" })
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -60,35 +60,27 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(DynamicList, _n("users"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("protocol")] = "mixed" })
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" })
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "naive" })
o:depends({ [_n("protocol")] = "anytls" })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "naive" })
o:depends({ [_n("protocol")] = "hysteria" })
o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "anytls" })
if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" })
@@ -97,20 +89,6 @@ if singbox_tags:find("with_quic") then
o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(ListValue, _n("hysteria_auth_type"), translate("Auth Type"))
o:value("disable", translate("Disable"))
o:value("string", translate("STRING"))
o:value("base64", translate("BASE64"))
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "string"})
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "base64"})
o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
o:depends({ [_n("protocol")] = "hysteria" })
@@ -174,10 +152,6 @@ if singbox_tags:find("with_quic") then
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" })
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
@@ -233,14 +207,8 @@ o.rewrite_option = "method"
for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
o:value(api.gen_uuid())
end
o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "tuic" })
o = s:option(Value, _n("ss_password"), translate("Password"))
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(ListValue, _n("flow"), translate("flow"))
o.default = ""
@@ -27,22 +27,11 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("auth")] = true })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o:depends({ [_n("auth")] = true })
o = s:option(Flag, _n("log"), translate("Log"))
@@ -32,8 +32,10 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
@@ -45,8 +45,10 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port"
o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o = s:option(ListValue, _n("user"), translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
@@ -0,0 +1,31 @@
api = require "luci.passwall.api"
appname = api.appname
fs = api.fs
api.set_default_cbi()
m = Map("passwall_server", translate("User Config"))
m.redirect = api.url("server")
api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
s = m:section(NamedSection, arg[1], "user", "")
s.addremove = false
s.dynamic = false
o = s:option(Value, "username", translate("Username"))
o.datatype = "and(uciname,maxlength(24))"
o.rmempty = false
o = s:option(Value, "password", translate("Password"))
o.rmempty = false
o = s:option(Value, "uuid", "UUID")
o.datatype = "uuid"
o.default = api.gen_uuid()
o.rmempty = false
return api.return_map(m)
@@ -369,6 +369,12 @@ function is_timehhmm(str)
end
datatypes.timehhmm = is_timehhmm
function is_uuid(str)
local pattern = "^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$"
return string.match(str, pattern) ~= nil
end
datatypes.uuid = is_uuid
function is_normal_node(e)
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
return false
@@ -102,29 +102,33 @@ local function start()
nft_file:write('flush chain inet fw4 PSW-SERVER\n')
nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n')
end
uci:foreach(CONFIG, "user", function(user)
local id = user[".name"]
local enable = user.enable
uci:foreach(CONFIG, "server", function(server)
local id = server[".name"]
local enable = server.enable
if enable and tonumber(enable) == 1 then
local enable_log = user.log
local enable_log = server.log
local log_path = nil
if enable_log and enable_log == "1" then
log_path = CONFIG_PATH .. "/" .. id .. ".log"
else
log_path = nil
end
local remarks = user.remarks
local port = tonumber(user.port)
local remarks = server.remarks
local port = tonumber(server.port)
local bin
local config = {}
local config_file = CONFIG_PATH .. "/" .. id .. ".json"
local udp_forward = 1
local type = user.type or ""
local type = server.type or ""
if type == "Socks" then
local auth = ""
if user.auth and user.auth == "1" then
local username = user.username or ""
local password = user.password or ""
if server.auth and server.auth == "1" then
local user = nil
if server.user then
user = uci:get_all("passwall_server", server.user)
end
local username = user and user.username or ""
local password = user and user.password or ""
if username ~= "" and password ~= "" then
username = "-u " .. username
password = "-P " .. password
@@ -133,59 +137,59 @@ local function start()
end
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
elseif type == "SSR" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
local udp_param = ""
udp_forward = tonumber(user.udp_forward) or 1
udp_forward = tonumber(server.udp_forward) or 1
if udp_forward == 1 then
udp_param = "-u"
end
type = type:lower()
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
elseif type == "SS-Rust" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
elseif type == "Xray" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
config.log.loglevel = user.loglevel
config.log.loglevel = server.loglevel
end
else
config = require(require_dir .. "util_xray").gen_config_server(user)
config = require(require_dir .. "util_xray").gen_config_server(server)
end
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
elseif type == "sing-box" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
config.log.timestamp = true
config.log.disabled = false
config.log.level = user.loglevel
config.log.level = server.loglevel
config.log.output = log_path
end
else
config = require(require_dir .. "util_sing-box").gen_config_server(user)
config = require(require_dir .. "util_sing-box").gen_config_server(server)
end
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
elseif type == "Hysteria2" then
if user.custom == "1" and user.config_str then
config = jsonc.parse(api.base64Decode(user.config_str))
if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(server.config_str))
else
config = require(require_dir .. "util_hysteria2").gen_config_server(user)
config = require(require_dir .. "util_hysteria2").gen_config_server(server)
end
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
end
@@ -203,7 +207,7 @@ local function start()
cmd(bin)
end
local bind_local = user.bind_local or 0
local bind_local = server.bind_local or 0
if bind_local and tonumber(bind_local) ~= 1 and port then
if nft_flag == "0" then
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
@@ -4,6 +4,20 @@ local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
users[user.username] = user.password
end
end
if not next(users) then
users = nil
end
end
local config = {
listen = (function()
if node.hysteria2_realms and node.hysteria2_realm_url then
@@ -25,10 +39,10 @@ function gen_config_server(node)
password = node.hysteria2_obfs_password
}
} or nil,
auth = {
type = "password",
password = node.hysteria2_auth_password
},
auth = users and {
type = "userpass",
userpass = users
} or nil,
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil,
down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil
@@ -4,9 +4,13 @@ local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local user = nil
if node.user then
user = uci:get_all("passwall2_server", node.user)
end
local config = {}
config.server_port = tonumber(node.port)
config.password = node.password
config.password = user and user.password or ""
config.timeout = tonumber(node.timeout)
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
config.method = node.method
@@ -766,39 +766,71 @@ function gen_config_server(node)
listen_port = tonumber(node.port),
}
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
local u = {}
if node.protocol == "mixed" or node.protocol == "socks" or node.protocol == "http" or node.protocol == "naive" then
u.username = user.username
u.password = user.password
end
if node.protocol == "shadowsocks" or node.protocol == "trojan" then
u.name = user.username
u.password = user.password
end
if node.protocol == "vmess" then
u.name = user.username
u.uuid = user.uuid
u.alterId = 0
end
if node.protocol == "vless" then
u.name = user.username
u.uuid = user.uuid
u.flow = node.flow
end
if node.protocol == "hysteria" then
u.name = user.username
u.auth_str = user.password
end
if node.protocol == "tuic" then
u.name = user.username
u.password = user.password
u.uuid = user.uuid
end
if node.protocol == "hysteria2" then
u.name = user.username
u.password = user.password
end
users[#users + 1] = u
end
end
if #users == 0 then
users = nil
end
end
local protocol_table = nil
if node.protocol == "mixed" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
users = users,
set_system_proxy = false
}
end
if node.protocol == "socks" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil
users = users
}
end
if node.protocol == "http" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
users = users,
tls = (node.tls == "1") and tls or nil,
}
end
@@ -806,21 +838,14 @@ function gen_config_server(node)
if node.protocol == "shadowsocks" then
protocol_table = {
method = node.method,
password = node.password,
password = node.ss_password,
users = users,
multiplex = mux,
}
end
if node.protocol == "vmess" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
alterId = 0,
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -831,15 +856,7 @@ function gen_config_server(node)
end
if node.protocol == "vless" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
flow = node.flow,
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -850,14 +867,7 @@ function gen_config_server(node)
end
if node.protocol == "trojan" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
password = node.uuid[i],
}
end
if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -870,15 +880,12 @@ function gen_config_server(node)
end
if node.protocol == "naive" then
protocol_table = {
users = {
{
username = node.username,
password = node.password
}
},
tls = tls,
}
if users then
protocol_table = {
users = users,
tls = tls
}
end
end
if node.protocol == "hysteria" then
@@ -888,13 +895,7 @@ function gen_config_server(node)
up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs,
users = {
{
name = "user1",
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
}
},
users = users,
recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil, --1.14 to stream_receive_window
recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil, --1.14 to connection_receive_window
max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil, --1.14 to max_concurrent_streams
@@ -904,31 +905,21 @@ function gen_config_server(node)
end
if node.protocol == "tuic" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
password = node.password
}
end
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
local alpn = {}
string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
protocol_table = {
users = users,
congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = tls
}
end
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
local alpn = {}
string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
protocol_table = {
users = users,
congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = tls
}
end
if node.protocol == "hysteria2" then
@@ -953,12 +944,7 @@ function gen_config_server(node)
end
return o
end)(node.hysteria2_obfs_type),
users = {
{
name = "user1",
password = node.hysteria2_auth_password or nil,
}
},
users = users,
ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
tls = tls,
realm = node.hysteria2_realms and (function()
@@ -978,15 +964,12 @@ function gen_config_server(node)
end
if node.protocol == "anytls" then
protocol_table = {
users = {
{
name = (node.username and node.username ~= "") and node.username or "sekai",
password = node.password
}
},
tls = tls,
}
if users then
protocol_table = {
users = users,
tls = tls,
}
end
end
if node.protocol == "direct" then
+47 -35
View File
@@ -488,17 +488,46 @@ function gen_config_server(node)
{ protocol = "freedom", tag = "direct", settings = { finalRules = {{ action = "allow" }}}}, { protocol = "blackhole", tag = "blocked" }
}
if node.protocol == "vmess" or node.protocol == "vless" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
id = node.uuid[i],
flow = (node.protocol == "vless"
and (node.tls == "1" or (node.decryption and node.decryption ~= "" and node.decryption ~= "none"))
and node.flow and node.flow ~= "") and node.flow or nil
}
local users = node.users or {}
local users = nil
if node.users and #node.users > 0 then
users = {}
for i, v in ipairs(node.users) do
local user = uci:get_all("passwall2_server", v) or {}
if user[".type"] == "user" then
local u = {}
if node.protocol == "socks" or node.protocol == "http" then
u.user = user.username
u.pass = user.password
end
if node.protocol == "shadowsocks" or node.protocol == "trojan" then
u.email = user.username
u.password = user.password
end
if node.protocol == "vmess" then
u.email = user.username
u.id = user.uuid
u.alterId = 0
end
if node.protocol == "vless" then
u.email = user.username
u.id = user.uuid
u.flow = node.flow
end
if node.protocol == "hysteria2" then
u.email = user.username
u.auth = user.password
end
users[#users + 1] = u
end
end
if #users == 0 then
users = nil
end
end
if node.protocol == "vmess" or node.protocol == "vless" then
if users then
settings = {
users = users,
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
@@ -507,40 +536,25 @@ function gen_config_server(node)
elseif node.protocol == "socks" then
settings = {
udp = ("1" == node.udp_forward) and true or false,
auth = ("1" == node.auth) and "password" or "noauth",
users = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
auth = users and "password" or "noauth",
users = users
}
elseif node.protocol == "http" then
settings = {
allowTransparent = false,
users = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
users = users
}
node.transport = "tcp"
node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then
settings = {
method = node.method,
password = node.password,
network = node.ss_network or "TCP,UDP"
password = node.ss_password,
users = users,
network = node.ss_network or "tcp,udp"
}
elseif node.protocol == "trojan" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
password = node.uuid[i],
}
end
if users then
settings = {
users = users
}
@@ -548,9 +562,7 @@ function gen_config_server(node)
elseif node.protocol == "hysteria2" then
settings = {
version = 2,
users = node.hysteria2_auth_password and {
{ auth = node.hysteria2_auth_password }
}
users = users
}
elseif node.protocol == "dokodemo-door" then
settings = {
@@ -1,6 +1,7 @@
<%
local api = require "luci.passwall.api"
local api = self.api
local appname = api.appname
local map = self.map
local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset"
-%>
@@ -500,15 +501,7 @@ table td, .table .td {
let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_node_order(group);
if (!arraysEqual(new_order, origin_group_node_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
break;
case "del_all":
@@ -652,48 +645,24 @@ table td, .table .td {
return ids;
}
function save_current_page_order(group) {
var table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table");
function set_order(group) {
let table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table");
if (!table) {
alert("<%:No table!%>");
return;
}
var rows = table.querySelectorAll("tr.cbi-section-table-row");
let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
alert("<%:No nodes!%>");
return;
}
var btn = document.getElementById("save_order_btn_" + group);
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
let ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
ajax.abortAll();
XHR.get('<%=api.url("save_node_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) {
btn.style.display = null;
btn.disabled = false;
}
if (x && x.status === 200) {
origin_group_node_order[group] = get_node_order(group);
alert("<%:Saved current page order successfully.%>");
if (btn) {
btn.style.display = "none";
}
} else {
alert("<%:Save failed!%>");
}
}
);
const input_order = document.getElementsByName(`group.order.${group}`)[0];
input_order.value = ids.join(" ");
}
//获取当前使用的节点
@@ -947,16 +916,7 @@ table td, .table .td {
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//save_current_page_order(group); // 自动提交保存
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_node_order(group);
if (!arraysEqual(new_order, origin_group_node_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
};
try {
@@ -1302,9 +1262,9 @@ table td, .table .td {
</tr>
{{node-tr}}
</table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="node_action('add')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</<%=section_tag%>>
</script>
@@ -1,7 +1,6 @@
<%
local api = require "luci.passwall.api"
local api = self.api
local appname = api.appname
local uci = api.uci
local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset"
local title_tag = is_js_luci and "h3" or "legend"
@@ -185,15 +184,7 @@ table td, .table .td {
let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
}
@@ -214,48 +205,24 @@ table td, .table .td {
return ids;
}
function save_current_page_order(group) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
function set_order(group) {
let table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
if (!table) {
alert("<%:No table!%>");
return;
}
var rows = table.querySelectorAll("tr.cbi-section-table-row");
let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
alert("<%:No shunt_rules!%>");
return;
}
var btn = document.getElementById("save_order_btn_" + group);
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
let ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
XHR.get('<%=api.url("save_shunt_rule_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) {
btn.style.display = null;
btn.disabled = false;
}
if (x && x.status === 200) {
// origin_group_shunt_rule_order[group] = get_shunt_rule_order(group);
alert("<%:Saved current page order successfully.%>");
// if (btn) {
// btn.style.display = "none";
// }
window.location.reload();
} else {
alert("<%:Save failed!%>");
}
}
);
const input_order = document.getElementsByName(`group.order.${group}`)[0];
input_order.value = ids.join(" ");
}
// List drag and rearrange
@@ -274,16 +241,7 @@ table td, .table .td {
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//save_current_page_order(group); // Auto save
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
set_order(group);
}
};
try {
@@ -339,10 +297,10 @@ table td, .table .td {
</tr>
{{shunt_rule-tr}}
</table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create">
<input type="text" id="add_name_{{group}}">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="toPage('add')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</<%=section_tag%>>
</script>
@@ -4,7 +4,7 @@ local api = self.api
<script type="text/javascript">
//<![CDATA[
let node_id = "<%=self.section%>";
let node_config_url = '<%=api.url("server_user")%>/' + node_id;
let node_config_url = '<%=api.url("server_config")%>/' + node_id;
document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=self.config%>"][name*="type"]', function(el) {
@@ -0,0 +1,38 @@
<%
local api = self.api
-%>
<script type="text/javascript">
//<![CDATA[
var _servers_status = document.getElementsByClassName('_servers_status');
for(var i = 0; i < _servers_status.length; i++) {
var id = _servers_status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.get('<%=api.url("server_status")%>', {
index: i,
id: id
},
function(x, result) {
_servers_status[result.index].setAttribute("style","font-weight:bold;");
_servers_status[result.index].setAttribute("color",result.status ? "green":"red");
_servers_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
var edit_btn = document.getElementById("cbi-<%=self.appname%>-<%=self.sectiontype%>").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
// Add `log` button
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_log")%>' + '?id=' + id + '\', \'_blank\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>
@@ -1,38 +0,0 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
var _users_status = document.getElementsByClassName('_users_status');
for(var i = 0; i < _users_status.length; i++) {
var id = _users_status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.get('<%=api.url("server_user_status")%>', {
index: i,
id: id
},
function(x, result) {
_users_status[result.index].setAttribute("style","font-weight:bold;");
_users_status[result.index].setAttribute("color",result.status ? "green":"red");
_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
var edit_btn = document.getElementById("cbi-passwall_server-user").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
//添加"日志"按钮
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_user_log")%>' + '?id=' + id + '\', \'_blank\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>
+12 -6
View File
@@ -490,12 +490,6 @@ msgstr "节点备注"
msgid "Add Mode"
msgstr "添加方式"
msgid "Save Order"
msgstr "保存当前顺序"
msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。"
msgid "Drag to reorder"
msgstr "拖动以重排"
@@ -1670,9 +1664,15 @@ msgstr "服务器端"
msgid "Server Config"
msgstr "服务器配置"
msgid "Servers Manager"
msgstr "服务器管理"
msgid "Users Manager"
msgstr "用户管理"
msgid "User Config"
msgstr "用户配置"
msgid "Logs"
msgstr "日志"
@@ -2109,9 +2109,15 @@ msgstr "使用自定义配置"
msgid "Custom Config"
msgstr "自定义配置"
msgid "Must be Base64 text!"
msgstr "必须是 Base64 内容!"
msgid "Must be JSON text!"
msgstr "必须是 JSON 文本内容!"
msgid "Must be UUID format!"
msgstr "必须是 UUID 格式!"
msgid "Geo View"
msgstr "Geo 查询"
+1 -1
View File
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION)
PKG_RELEASE:=15
PKG_RELEASE:=16
endif
LUCI_TITLE:=Footstrap Theme
@@ -684,9 +684,9 @@ function takeReturn() {
return true;
} catch (e) { return false; }
}
const MARK = 'fs-ap';
const MARK = 'fs-ap'; /* the built form's own class; also how mount() knows it is already there */
/* how long the stock view gets to render its tabs before a missing group counts as a failure */
const TAB_DEADLINE = 5000; /* the built form's own class; also how mount() knows it is already there */
const TAB_DEADLINE = 5000;
const TAB = 'fs-appearance'; /* the pane's data-tab, which ui.tabs' click handler matches on */
let _routeObserver = null, _viewObserver = null, _observedView = null, _building = false;
@@ -23,8 +23,18 @@ const _fitters = [];
let _rafPending = false;
let _ro = null, _mo = null;
/* one canvas for the whole document: wordFloor() measures text with it, and creating one per call
* is the expensive half of that measurement */
* is the expensive half of that measurement. `null` = not asked yet, `false` = asked and refused —
* the same three-state slot fs-widgets.js's rasterCtx() keeps, and for the same reason: a browser
* with canvas turned off (an anti-fingerprinting extension, a WebView, memory pressure) answers
* null, and retrying that on every fit pass is a cost with no chance of a different answer. */
let _cx = null;
function textCtx() {
if (_cx === null) {
try { _cx = document.createElement('canvas').getContext('2d') || false; }
catch (e) { _cx = false; }
}
return _cx;
}
const SPACE_RE = /\s+/;
/* Run every fitter NOW, synchronously. A fitter must be idempotent — this fires on every
@@ -155,7 +165,15 @@ return baseclass.extend({
wordFloor(t) {
const rows = t.querySelectorAll('.tr:not(.table-titles):not(.cbi-section-table-titles):not(.placeholder)');
if (!rows.length) return 0;
if (!_cx) _cx = document.createElement('canvas').getContext('2d');
/* No 2D context, no floor to report — and 0 is the honest answer, not a failure: it says
* "this test has nothing to add", leaving the other two stack tests (does it overflow, is a
* one-token column shredded) to decide. Throwing here would cost far more than the test is
* worth: fs-select's fitTables() strips `fs-stacked` BEFORE it measures (rule 1), and this is
* the last term of that `||`, so the throw would escape the row walk with the class already
* gone — every stackable table left un-stacked, every later table in the pass unfitted, once
* a second on a polled page. */
const cx = textCtx();
if (!cx) return 0;
const floors = [], longest = [];
let cols = null;
for (const row of rows) {
@@ -181,8 +199,8 @@ return baseclass.extend({
else for (const w of text.split(SPACE_RE)) if (w.length > word.length) word = w;
if (!(word.length > (longest[i] || 0))) continue;
longest[i] = word.length;
_cx.font = col.font;
const need = _cx.measureText(word).width + (col.tracking * word.length) + col.pad;
cx.font = col.font;
const need = cx.measureText(word).width + (col.tracking * word.length) + col.pad;
if (!(floors[i] >= need)) floors[i] = need;
}
}
@@ -61,20 +61,39 @@ function clearViewIntervals() {
* is the pair disagreeing: a tick that is running while the id it runs on has no name we know.
* Then do NOTHING, once, loudly: a view's leftover interval outliving its page costs a wasted
* RPC, whereas clearing LuCI's own tick costs every live value on every later page. */
/* …and the ALIAS ITSELF is the first thing that can go — it is the deprecation the paragraph
* above is about, so this function cannot be the one place that reads it blind. Every other
* `L.Poll` read in this file is guarded, and an unguarded one here throws where a throw costs
* most: navigate() calls this AFTER #view has been swapped for the "Loading view…" spinner and
* BEFORE data-page, pushState and the require, so a TypeError would strand every click on the
* spinner with the old URL and the old menu mark — far worse than the leaked interval this
* exists to sweep. No alias is the same answer as an unreadable timer: do NOTHING, once,
* loudly. */
if (!L.Poll) {
warnPollUnreadable('footstrap: L.Poll is gone from this luci-base, so LuCI\'s own tick cannot be '
+ 'told apart from a view\'s timers — leaving view intervals alone. fs-router.js needs '
+ 'updating for this luci-base.');
return;
}
const running = (typeof L.Poll.active === 'function') ? L.Poll.active() : (L.Poll.timer != null);
if (running && L.Poll.timer == null) {
if (!_pollTimerWarned) {
_pollTimerWarned = true;
console.error('footstrap: LuCI is polling but L.Poll.timer is not readable — leaving view '
+ 'intervals alone rather than risking its tick. fs-router.js needs updating for this '
+ 'luci-base.');
}
warnPollUnreadable('footstrap: LuCI is polling but L.Poll.timer is not readable — leaving view '
+ 'intervals alone rather than risking its tick. fs-router.js needs updating for this '
+ 'luci-base.');
return;
}
const keep = running ? L.Poll.timer : null;
_viewIntervals.forEach((id) => { if (id !== keep) window.clearInterval(id); });
}
/* ONE line per document whatever went wrong: this runs on every navigation, and a router that
* cannot read L.Poll cannot read it on the next click either — a message per click would bury the
* console the user is reading it in. */
let _pollTimerWarned = false;
function warnPollUnreadable(msg) {
if (_pollTimerWarned) return;
_pollTimerWarned = true;
console.error(msg);
}
/* --- uci cache teardown for SPA nav ---
* `uci.load()` does not answer "is this config present?" — it answers "which of these packages did
@@ -806,15 +825,23 @@ function navigate(pathname, push, kbd) {
* repairStaleRender() a mess that only exists because a require in flight cannot be stopped. */
if (gen !== _navGen) return null;
_seen.add(className);
/* NAME THE OWNER for the length of this require. On a first visit the require IS the render,
* so any <style> the module injects belongs to THIS page — even if a newer navigation has
* stamped data-page by the time it lands, which is exactly the case repairStaleRender() below
* exists for. Without it fs-sheets credited such a sheet to the page that superseded this one
* and bound it there for the life of the document (measured: luci-app-filemanager's
* `.cbi-button-save { display: none !important }` disabled on its own page and live on
* System -> System, across return visits). */
sheets.attributeTo(rsegs);
return RT.require(className).finally(() => sheets.attributeTo(null));
/* NAME THE OWNER for the length of this require, AND ONLY WHEN THE MODULE HAS YET TO BE
* EVALUATED. On a first visit the require IS the render, so any <style> the module injects
* belongs to THIS page — even if a newer navigation has stamped data-page by the time it
* lands, which is exactly the case repairStaleRender() below exists for. Without it fs-sheets
* credited such a sheet to the page that superseded this one and bound it there for the life
* of the document (measured: luci-app-filemanager's `.cbi-button-save { display: none
* !important }` disabled on its own page and live on System -> System, across return visits).
*
* `!cached` is what makes the hint survive long enough to be read, and its absence is why the
* measured case stopped being covered. A CACHED require injects nothing — the module was
* evaluated on the first visit and `require()` hands back the singleton — but it still ran
* this line, and its `.finally` fires within a microtask, so the navigation that superseded a
* cold require would set the hint to itself and clear it to null while that require was still
* in flight. The sheet then landed with no hint at all, i.e. credited to the superseding page,
* which is the exact bug. A require that cannot inject has no business naming an owner. */
if (!cached) sheets.attributeTo(rsegs, gen);
return RT.require(className).finally(() => { if (!cached) sheets.attributeTo(null, gen); });
}).then((view) => {
if (view == null) return;
if (!(view instanceof RT.view))
@@ -185,8 +185,21 @@ function enhance(sel) {
* openclash / justclash / ssclash / dashboard / statistics included, found ZERO. That is the point:
* every table anyone here emits already carries the class, so this changes nothing that can be
* measured and covers the one shape that cannot be (docs/conventions.md: coverage is a contract). */
const FOREIGN_TABLE = '#view .table:not(.cbi-section-table):not(.fs-dt), ' +
'#view table:not(.table):not(.cbi-section-table):not(.fs-dt)';
/* NO `:not(.fs-dt)`, and that is the difference between claiming a table once and KEEPING it. These
* tables are polled: L.ui.Table.update() and every hand-rolled equivalent replace the rows inside
* the element they already have. Excluding what we tagged meant the claim ran exactly once per
* element — the rows present at that moment were adopted and captioned, and every batch after it
* kept neither `.tr`/`.td` nor `data-title`, on a table that by then may carry `.fs-stacked`, where
* `#view .table.fs-dt.fs-stacked { overflow: hidden }` clips the lot with no scrollbar. That is the
* exact failure adoptMarkup() was written to prevent, arriving one poll later. LuCI's own tables
* were never exposed to it (ui.Table writes those class names and the caption itself), so this is
* third-party-only — which is the zone this whole selector exists for.
*
* Re-running is what the two functions below are already written for: both are additive, both skip
* what is already done, and neither re-decides anything (adoptMarkup() settles the "is this ours to
* rewrite?" question once, at claim time, and remembers the answer on the element). */
const FOREIGN_TABLE = '#view .table:not(.cbi-section-table), ' +
'#view table:not(.table):not(.cbi-section-table)';
/* The FOURTH header markup, and the one only a foreign table produces: `<table><tr><th>…`, with no
* `<thead>` for the parser to imply and none of LuCI's class names. It is the exact shape the phone
@@ -255,9 +268,10 @@ function tagDataTables() {
* names: a `<thead>` becomes `.thead` and a plain first row of `<th>` becomes `.tr.table-titles` —
* the two names theme/30-tables.css hides when stacked. */
function adoptMarkup(t, head) {
/* DECIDED ONCE, AT CLAIM TIME, and only for a table that speaks none of this vocabulary. Asking
* the question every pass instead would answer "already adopted" the moment we adopted it — and
* these tables are polled, so the fresh rows that arrive bare afterwards would never be taken.
/* DECIDED ONCE, AT CLAIM TIME, and only for a table that speaks none of this vocabulary — then
* READ on every pass, because the caller now revisits a table it has already claimed (see
* FOREIGN_TABLE). Asking the question afresh each pass instead would answer "already adopted"
* the moment we adopted it, and the fresh rows a poll brings in bare would never be taken.
* Asking it at all is what keeps the theme's hands off LuCI's own markup: the apk Software list
* heads its table with `.tr.cbi-section-table-titles`, and blindly adding `table-titles` to that
* would be the theme rewriting a class LuCI chose. */
@@ -676,11 +676,30 @@ function appKey(segs) {
* stand: the System page came back with no Save button, and stayed that way across return visits).
*
* So the router names the owner for the duration of such a require (see fs-router.js), and this is
* that hint. It is a plain value rather than a stack: two cold requires cannot overlap, because the
* second navigation supersedes the first before it starts one. */
* that hint.
*
* ONE SLOT, STAMPED WITH THE NAVIGATION THAT SET IT. The router only names an owner for a require
* that has yet to evaluate its module, so the common shape — a cold require in flight, the user
* clicking on to a page already in LuCI's class cache — no longer touches this slot at all. Two
* COLD requires can still overlap, and then the newer one wins the slot: it is the page the user is
* looking at, and crediting ITS sheet to the page it superseded would leave the visible page
* unpainted, which is the worse of the two errors. The generation stamp is what keeps that from
* getting worse still — the older require's `.finally` must not clear a slot the newer one now
* holds, or the newer page's own sheet lands unattributed.
*
* There is no way to do better from here: LuCI evaluates a view module inside `eval()` in its own
* `require()` (luci.js), so nothing observable says WHICH module is running when a <style> appears —
* `document.currentScript` is null and the injection is synchronous inside a promise chain we do
* not own. The remaining hole is therefore two cold requires overlapping, where the first one's
* sheet is credited to the second's page — the pre-existing behaviour, now narrowed to that one
* case instead of every navigation away from a cold require. */
let _ownerHint = null;
function attributeTo(segs) {
let _ownerGen = -1;
function attributeTo(segs, gen) {
/* a stale require letting go of a slot somebody else now holds: leave it alone */
if (segs == null && gen !== _ownerGen) return;
_ownerHint = (segs == null) ? null : appKey(segs);
_ownerGen = (segs == null) ? -1 : gen;
}
function ownerKey() {
@@ -723,6 +742,25 @@ function scopeToCurrentPage(segs) {
});
}
/* TAKE A SHEET, AND SCOPE IT IN THE SAME BREATH.
*
* scopeToCurrentPage() runs on NAVIGATION, so it only ever sees sheets that were already here. A
* sheet that arrives afterwards is scoped by nobody until the next click — and the one case where
* that matters is precisely the case the owner hint exists for: a cold require whose page the user
* has already left injects its <style> into a document showing somebody else's page, and the sheet
* paints there until the user navigates again. Measured with a view whose module appends
* `body { outline: 3px solid rgb(9,9,9) }`: correctly credited to its own page, and still painting
* the outline on the page that superseded it.
*
* So the stamp and the switch are one act. `ownerKey()` is the page the sheet BELONGS to and
* `currentKey()` the page on screen; on every ordinary arrival they are the same string and this is
* a no-op. */
function claimOwner(el) {
const key = ownerKey();
_owner.set(el, key);
if (outlivesPage(el)) setEnabled(el, key === currentKey());
}
function rehostIntoThemeLayer(el, universe) {
if (el.dataset.fsLayered) return;
@@ -735,7 +773,7 @@ function rehostIntoThemeLayer(el, universe) {
el.dataset.fsLayered = '1';
el.after(s); /* keep source order: ties inside the layer still resolve as they did */
silence(el);
_owner.set(s, ownerKey()); /* the shim paints; the original is silenced for good */
claimOwner(s); /* the shim paints; the original is silenced for good */
fenceImported(s, universe.names, Date.now() + 1000); /* a cache hit lands on the first frame */
return;
}
@@ -752,7 +790,6 @@ function rehostIntoThemeLayer(el, universe) {
* unpinned all over again and a second pass appends a second fence. The mark is the only thing
* that says the work is done, so it has to be set for every path below, wrapped or not. */
el.dataset.fsLayered = '1';
_owner.set(el, ownerKey()); /* a <style> is re-hosted IN PLACE, so it paints itself */
/* Wrap only if the text still IS the sheet (see textIsSheet). When it is not, the sheet stays
* unlayered — Zone 2 exactly where it already was, which is a trade — rather than lose rules,
@@ -769,6 +806,15 @@ function rehostIntoThemeLayer(el, universe) {
el.textContent = '@layer theme {\n' + el.textContent + '\n}';
}
try { if (el.sheet) fenceRules(el.sheet.cssRules, universe.names); } catch (e) { /* unfenced, not broken */ }
/* LAST, because the line above may have re-parsed the sheet. `el.disabled` is not a content
* attribute — it is the element's view of `el.sheet.disabled`, and assigning textContent throws
* the old CSSStyleSheet away and builds a new one, which comes back ENABLED. Claiming before the
* wrap therefore switched a sheet off and then switched it back on within the same call, which is
* how a sheet belonging to a page the user had left went on painting the page that superseded it
* (measured: `body { outline: 3px solid rgb(9,9,9) }` from a still-loading view, live on
* System -> System). Ownership is recorded from the same call either way — ownerKey() is read
* here, while the router's hint still names the page whose module is evaluating. */
claimOwner(el); /* a <style> is re-hosted IN PLACE, so it paints itself */
}
/* Re-hosting needs the theme's own selectors to tell an invasive sheet from an inert one. If
@@ -77,6 +77,46 @@
--fs-alert-tint: var(--fs-accent-fill);
}
/* ---- A SPINNING ALERT IS A GLYPH AND A MESSAGE, SIDE BY SIDE ----
*
* The spinner is a `::before` on the element carrying `.spinning` (base/95-luci.css), and it is
* `inline-block` so that it sits in the flow of the button labels and `em.spinning` texts that
* are its usual hosts. This host is neither: `ui.changes.displayStatus()` builds the apply /
* revert / rollback dialog as `showModal('','')` + `classList.add('alert-message', 'notice',
* 'spinning')` + `dom.content(message, E('p', …))`, i.e. a BLOCK box whose content is a BLOCK
* element — so the glyph opened a line box of its own and the message went underneath it,
* against the dialog's start edge. Reported on the luci PR with a screenshot of "Aplicando
* cambios de configuración… 88s" sitting a line below its spinner.
*
* Grid rather than flex, because the caller decides how many children there are and flex would
* lay a second one out BESIDE the first: `displayStatus()` passes one `<p>` today but also
* builds `[E('h4', …), E('p', …)]` for its non-spinning states, and `addNotification(title,
* children, …classes)` takes `spinning` from anyone. Two columns — glyph, then everything else
* — puts the glyph beside the first row and stacks the remaining children under it, for any
* number of them. Measured on 25.12 at 1440px, the three shapes a caller can produce (one `<p>`,
* `h4` + `<p>`, bare text): the content used to start 24.6 / 20.6 / 19.5px BELOW the glyph's
* line and now starts on it, and the one-paragraph dialog is 57.5px tall instead of 78.1px.
*
* `align-items: center` centres the 20px glyph against the first row rather than pinning it to
* the row's top, which is where it reads as "this line is in progress".
*
* The notification variant is untouched by all this and deliberately so: ui.js writes
* `style="display:flex"` on the element itself, and an inline style beats a layered rule, so
* those keep their own flex row — where the glyph is already a flex item on the message's line. */
.alert-message.spinning {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
/* `.alert-message`'s own `gap` above is the row gap between stacked children; the column gap
* is the glyph's own spacing and is the smaller step, so it is stated rather than inherited
* from that shorthand. */
column-gap: var(--fs-space-2);
}
/* the glyph is a grid item now, so its inline margin would double the column gap; and it is
* pinned to row 1 so that it stays beside the FIRST child however many follow */
.alert-message.spinning::before { grid-row: 1; margin-inline-end: 0; }
.alert-message.spinning > * { grid-column: 2; }
/* h2 is what the theme's own notices emit (the document had no heading above h4 at all); h4 is
* here because a third-party alert may use it, and coverage only grows. h5/h6 joined for the
* same reason — base styled them and the theme did not, so an alert heading looked like an
+2 -2
View File
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=natflow
PKG_VERSION:=20260531
PKG_RELEASE:=64
PKG_RELEASE:=65
PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=b3e749ae53becc4d31596e40d18e4875e518adc1
PKG_SOURCE_VERSION:=07142a20690b250747bcd0ded13586a5ee5a8020
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=nexttrace
PKG_VERSION:=1.7.1
PKG_RELEASE:=12
PKG_VERSION:=1.7.2
PKG_RELEASE:=13
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?