Sync 2026-08-13 21:00:03

This commit is contained in:
github-actions[bot]
2026-08-13 21:00:03 +08:00
parent c57dcedc98
commit 29133c66a8
48 changed files with 1339 additions and 1967 deletions
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=dae PKG_NAME:=dae
PKG_VERSION:=2026.08.05 PKG_VERSION:=2026.08.13
PKG_RELEASE:=34 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_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip PKG_HASH:=skip
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=daed PKG_NAME:=daed
PKG_VERSION:=2026.08.05 PKG_VERSION:=2026.08.13
PKG_RELEASE:=45 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_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip PKG_HASH:=skip
@@ -211,7 +211,7 @@ function parseShareLink(uri, features) {
case 'socks': case 'socks':
case 'socks4': case 'socks4':
case 'socks4a': case 'socks4a':
case 'socsk5': case 'socks5':
case 'socks5h': case 'socks5h':
url = new URL('http://' + uri[1]); url = new URL('http://' + uri[1]);
@@ -52,8 +52,7 @@
8.150.64.0/23 8.150.64.0/23
8.152.0.0/13 8.152.0.0/13
8.160.0.0/15 8.160.0.0/15
8.162.0.0/18 8.162.0.0/17
8.162.64.0/19
8.163.0.0/16 8.163.0.0/16
8.164.0.0/14 8.164.0.0/14
8.168.0.0/15 8.168.0.0/15
@@ -67,7 +66,6 @@
14.144.0.0/12 14.144.0.0/12
14.204.0.0/15 14.204.0.0/15
14.208.0.0/12 14.208.0.0/12
14.241.232.0/21
14.255.16.0/24 14.255.16.0/24
14.255.238.0/24 14.255.238.0/24
14.255.254.0/24 14.255.254.0/24
@@ -312,6 +310,7 @@
43.248.108.0/22 43.248.108.0/22
43.248.112.0/21 43.248.112.0/21
43.248.128.0/20 43.248.128.0/20
43.248.184.0/21
43.248.192.0/20 43.248.192.0/20
43.248.232.0/22 43.248.232.0/22
43.248.244.0/22 43.248.244.0/22
@@ -485,7 +484,7 @@
52.82.134.0/23 52.82.134.0/23
52.82.136.0/24 52.82.136.0/24
52.82.144.0/23 52.82.144.0/23
52.82.148.0/22 52.82.148.0/23
52.82.160.0/21 52.82.160.0/21
52.82.170.0/23 52.82.170.0/23
52.82.172.0/22 52.82.172.0/22
@@ -562,7 +561,7 @@
59.111.18.0/23 59.111.18.0/23
59.111.21.0/24 59.111.21.0/24
59.111.22.0/24 59.111.22.0/24
59.111.24.0/21 59.111.24.0/22
59.111.32.0/24 59.111.32.0/24
59.111.35.0/24 59.111.35.0/24
59.111.36.0/24 59.111.36.0/24
@@ -577,7 +576,9 @@
59.111.128.0/20 59.111.128.0/20
59.111.144.0/24 59.111.144.0/24
59.111.152.0/21 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.168.0/22
59.111.173.0/24 59.111.173.0/24
59.111.175.0/24 59.111.175.0/24
@@ -623,18 +624,12 @@
60.253.128.0/17 60.253.128.0/17
60.255.0.0/16 60.255.0.0/16
61.4.80.0/23 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.20.0/24
61.28.22.0/24 61.28.22.0/24
61.28.49.0/24 61.28.49.0/24
61.28.108.0/24 61.28.108.0/24
61.28.112.0/20 61.28.112.0/20
61.29.128.0/18 61.29.161.0/24
61.47.128.0/18 61.47.128.0/18
61.48.0.0/13 61.48.0.0/13
61.87.192.0/18 61.87.192.0/18
@@ -731,6 +726,7 @@
101.124.0.0/20 101.124.0.0/20
101.124.19.0/24 101.124.19.0/24
101.124.22.0/24 101.124.22.0/24
101.124.24.0/21
101.124.62.0/24 101.124.62.0/24
101.125.0.0/22 101.125.0.0/22
101.125.4.0/23 101.125.4.0/23
@@ -775,10 +771,9 @@
101.237.128.0/20 101.237.128.0/20
101.240.0.0/14 101.240.0.0/14
101.245.0.0/17 101.245.0.0/17
101.245.128.0/20 101.245.128.0/19
101.245.144.0/22
101.245.152.0/21
101.245.160.0/20 101.245.160.0/20
101.245.176.0/22
101.246.172.0/22 101.246.172.0/22
101.246.176.0/20 101.246.176.0/20
101.248.0.0/15 101.248.0.0/15
@@ -793,6 +788,8 @@
101.254.32.0/19 101.254.32.0/19
101.254.64.0/18 101.254.64.0/18
101.254.128.0/17 101.254.128.0/17
102.141.200.0/24
102.220.90.0/24
103.1.8.0/22 103.1.8.0/22
103.1.20.0/22 103.1.20.0/22
103.1.168.0/22 103.1.168.0/22
@@ -1151,6 +1148,7 @@
103.144.66.0/23 103.144.66.0/23
103.144.70.0/24 103.144.70.0/24
103.144.158.0/23 103.144.158.0/23
103.144.244.0/24
103.145.42.0/23 103.145.42.0/23
103.146.126.0/23 103.146.126.0/23
103.147.124.0/24 103.147.124.0/24
@@ -1188,7 +1186,6 @@
103.170.4.0/23 103.170.4.0/23
103.170.212.0/23 103.170.212.0/23
103.174.94.0/23 103.174.94.0/23
103.177.28.0/23
103.179.78.0/23 103.179.78.0/23
103.181.234.0/24 103.181.234.0/24
103.183.66.0/23 103.183.66.0/23
@@ -1222,6 +1219,8 @@
103.200.144.0/21 103.200.144.0/21
103.200.220.0/22 103.200.220.0/22
103.200.224.0/24 103.200.224.0/24
103.200.228.0/24
103.200.232.0/24
103.201.192.0/24 103.201.192.0/24
103.201.196.0/24 103.201.196.0/24
103.201.200.0/24 103.201.200.0/24
@@ -1255,6 +1254,7 @@
103.212.4.0/22 103.212.4.0/22
103.212.12.0/22 103.212.12.0/22
103.212.48.0/23 103.212.48.0/23
103.212.50.0/24
103.212.109.0/24 103.212.109.0/24
103.213.49.0/24 103.213.49.0/24
103.213.50.0/23 103.213.50.0/23
@@ -1343,6 +1343,7 @@
103.236.56.0/21 103.236.56.0/21
103.236.64.0/19 103.236.64.0/19
103.236.96.0/22 103.236.96.0/22
103.236.116.0/23
103.236.120.0/22 103.236.120.0/22
103.236.244.0/22 103.236.244.0/22
103.236.248.0/21 103.236.248.0/21
@@ -1379,6 +1380,7 @@
103.240.124.0/22 103.240.124.0/22
103.240.244.0/22 103.240.244.0/22
103.241.95.0/24 103.241.95.0/24
103.241.220.0/22
103.242.168.0/23 103.242.168.0/23
103.242.172.0/22 103.242.172.0/22
103.242.212.0/22 103.242.212.0/22
@@ -1409,12 +1411,11 @@
103.251.96.0/22 103.251.96.0/22
103.251.124.0/22 103.251.124.0/22
103.251.160.0/22 103.251.160.0/22
103.251.204.0/24 103.251.204.0/23
103.251.207.0/24 103.251.207.0/24
103.251.240.0/22 103.251.240.0/22
103.252.36.0/24 103.252.36.0/24
103.252.172.0/22 103.252.172.0/22
103.252.248.0/22
103.253.60.0/22 103.253.60.0/22
103.253.204.0/22 103.253.204.0/22
103.254.68.0/22 103.254.68.0/22
@@ -1425,6 +1426,7 @@
103.255.92.0/22 103.255.92.0/22
103.255.140.0/22 103.255.140.0/22
103.255.200.0/22 103.255.200.0/22
104.251.176.0/22
106.0.4.0/22 106.0.4.0/22
106.2.37.0/24 106.2.37.0/24
106.2.40.0/23 106.2.40.0/23
@@ -1445,7 +1447,6 @@
106.2.100.0/22 106.2.100.0/22
106.2.104.0/21 106.2.104.0/21
106.2.112.0/21 106.2.112.0/21
106.2.124.0/22
106.2.128.0/19 106.2.128.0/19
106.3.0.0/19 106.3.0.0/19
106.3.32.0/20 106.3.32.0/20
@@ -1655,7 +1656,6 @@
113.130.112.0/21 113.130.112.0/21
113.132.0.0/14 113.132.0.0/14
113.136.0.0/13 113.136.0.0/13
113.164.25.0/24
113.194.0.0/15 113.194.0.0/15
113.197.101.0/24 113.197.101.0/24
113.197.103.0/24 113.197.103.0/24
@@ -1895,7 +1895,9 @@
116.198.160.0/20 116.198.160.0/20
116.198.176.0/21 116.198.176.0/21
116.198.192.0/18 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.204.0.0/17
116.205.0.0/16 116.205.0.0/16
116.207.0.0/16 116.207.0.0/16
@@ -2063,7 +2065,10 @@
118.193.96.0/19 118.193.96.0/19
118.194.32.0/19 118.194.32.0/19
118.194.134.0/23 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.195.128.0/17
118.196.0.0/16 118.196.0.0/16
118.197.0.0/18 118.197.0.0/18
@@ -2095,6 +2100,7 @@
119.18.208.0/21 119.18.208.0/21
119.18.224.0/24 119.18.224.0/24
119.18.226.0/24 119.18.226.0/24
119.18.234.0/24
119.18.237.0/24 119.18.237.0/24
119.18.239.0/24 119.18.239.0/24
119.23.0.0/16 119.23.0.0/16
@@ -2102,8 +2108,8 @@
119.28.28.0/24 119.28.28.0/24
119.29.0.0/16 119.29.0.0/16
119.31.192.0/19 119.31.192.0/19
119.32.0.0/15 119.32.0.0/18
119.34.0.0/16 119.33.194.0/23
119.36.0.0/16 119.36.0.0/16
119.37.0.0/23 119.37.0.0/23
119.37.2.0/24 119.37.2.0/24
@@ -2127,8 +2133,7 @@
119.44.16.0/22 119.44.16.0/22
119.44.20.0/23 119.44.20.0/23
119.44.23.0/24 119.44.23.0/24
119.44.24.0/22 119.44.24.0/23
119.44.28.0/23
119.44.45.0/24 119.44.45.0/24
119.44.46.0/24 119.44.46.0/24
119.44.50.0/23 119.44.50.0/23
@@ -2137,8 +2142,6 @@
119.44.192.0/24 119.44.192.0/24
119.44.200.0/24 119.44.200.0/24
119.44.205.0/24 119.44.205.0/24
119.44.208.0/22
119.44.212.0/23
119.44.214.0/24 119.44.214.0/24
119.44.217.0/24 119.44.217.0/24
119.44.218.0/23 119.44.218.0/23
@@ -2488,7 +2491,7 @@
123.254.96.0/21 123.254.96.0/21
124.14.5.0/24 124.14.5.0/24
124.14.8.0/24 124.14.8.0/24
124.14.12.0/24 124.14.12.0/23
124.14.15.0/24 124.14.15.0/24
124.14.22.0/24 124.14.22.0/24
124.14.64.0/18 124.14.64.0/18
@@ -2558,7 +2561,6 @@
125.98.128.0/17 125.98.128.0/17
125.104.0.0/13 125.104.0.0/13
125.112.0.0/12 125.112.0.0/12
125.171.0.0/16
125.208.0.0/19 125.208.0.0/19
125.208.32.0/20 125.208.32.0/20
125.208.49.0/24 125.208.49.0/24
@@ -2736,7 +2738,6 @@
156.227.239.0/24 156.227.239.0/24
156.232.9.0/24 156.232.9.0/24
156.232.10.0/23 156.232.10.0/23
156.242.5.0/24
157.0.0.0/16 157.0.0.0/16
157.10.35.0/24 157.10.35.0/24
157.10.112.0/23 157.10.112.0/23
@@ -2793,6 +2794,7 @@
163.61.202.0/23 163.61.202.0/23
163.61.214.0/23 163.61.214.0/23
163.125.0.0/16 163.125.0.0/16
163.128.254.0/23
163.142.0.0/16 163.142.0.0/16
163.177.0.0/16 163.177.0.0/16
163.179.0.0/16 163.179.0.0/16
@@ -2802,6 +2804,11 @@
163.244.246.0/24 163.244.246.0/24
165.101.70.0/24 165.101.70.0/24
165.101.208.0/23 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 166.111.0.0/16
167.139.0.0/16 167.139.0.0/16
167.220.244.0/22 167.220.244.0/22
@@ -2944,6 +2951,7 @@
183.78.180.0/22 183.78.180.0/22
183.81.180.0/22 183.81.180.0/22
183.84.0.0/15 183.84.0.0/15
183.91.37.0/24
183.91.39.0/24 183.91.39.0/24
183.91.40.0/21 183.91.40.0/21
183.91.48.0/21 183.91.48.0/21
@@ -3017,9 +3025,10 @@
202.47.104.0/21 202.47.104.0/21
202.51.32.0/20 202.51.32.0/20
202.55.0.0/19 202.55.0.0/19
202.57.192.0/24 202.57.192.0/23
202.57.196.0/22 202.57.196.0/22
202.57.200.0/23 202.57.200.0/23
202.57.202.0/24
202.57.204.0/23 202.57.204.0/23
202.59.213.0/24 202.59.213.0/24
202.59.214.0/23 202.59.214.0/23
@@ -3135,8 +3144,10 @@
203.8.30.0/24 203.8.30.0/24
203.12.91.0/24 203.12.91.0/24
203.12.93.0/24 203.12.93.0/24
203.12.95.0/24
203.13.81.0/24 203.13.81.0/24
203.15.0.0/20 203.15.0.0/20
203.15.114.0/24
203.25.52.0/24 203.25.52.0/24
203.25.208.0/20 203.25.208.0/20
203.32.48.0/23 203.32.48.0/23
@@ -3303,6 +3314,7 @@
203.95.2.0/24 203.95.2.0/24
203.95.4.0/22 203.95.4.0/22
203.95.128.0/23 203.95.128.0/23
203.99.18.0/24
203.99.21.0/24 203.99.21.0/24
203.99.22.0/23 203.99.22.0/23
203.99.26.0/23 203.99.26.0/23
@@ -3321,7 +3333,7 @@
203.107.72.0/21 203.107.72.0/21
203.107.80.0/21 203.107.80.0/21
203.107.96.0/23 203.107.96.0/23
203.107.116.0/22 203.107.112.0/21
203.107.120.0/22 203.107.120.0/22
203.107.124.0/23 203.107.124.0/23
203.110.160.0/19 203.110.160.0/19
@@ -3393,8 +3405,8 @@
203.195.128.0/17 203.195.128.0/17
203.196.0.0/21 203.196.0.0/21
203.202.236.0/23 203.202.236.0/23
203.205.88.0/23 203.205.88.0/22
203.205.90.0/24 203.205.95.0/24
203.207.64.0/19 203.207.64.0/19
203.207.96.0/21 203.207.96.0/21
203.207.104.0/22 203.207.104.0/22
@@ -3506,8 +3518,7 @@
210.79.224.0/19 210.79.224.0/19
210.82.0.0/15 210.82.0.0/15
210.87.128.0/18 210.87.128.0/18
210.185.192.0/18 210.192.96.0/21
210.192.96.0/20
210.192.116.0/22 210.192.116.0/22
210.192.120.0/21 210.192.120.0/21
211.64.0.0/13 211.64.0.0/13
@@ -3611,7 +3622,8 @@
211.147.208.0/20 211.147.208.0/20
211.147.224.0/19 211.147.224.0/19
211.148.0.0/18 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.149.128.0/17
211.150.0.0/18 211.150.0.0/18
211.150.64.0/19 211.150.64.0/19
@@ -3644,7 +3656,6 @@
211.155.240.0/20 211.155.240.0/20
211.156.0.0/19 211.156.0.0/19
211.156.128.0/19 211.156.128.0/19
211.156.176.0/20
211.156.192.0/20 211.156.192.0/20
211.156.208.0/21 211.156.208.0/21
211.156.216.0/22 211.156.216.0/22
@@ -3785,8 +3796,6 @@
218.247.64.0/19 218.247.64.0/19
218.247.128.0/17 218.247.128.0/17
218.249.0.0/16 218.249.0.0/16
218.252.0.0/17
218.253.0.0/18
219.72.0.0/16 219.72.0.0/16
219.82.0.0/16 219.82.0.0/16
219.83.160.0/19 219.83.160.0/19
@@ -3823,6 +3832,7 @@
219.235.32.0/19 219.235.32.0/19
219.235.64.0/18 219.235.64.0/18
219.235.128.0/19 219.235.128.0/19
219.235.164.0/23
219.235.192.0/23 219.235.192.0/23
219.235.207.0/24 219.235.207.0/24
219.235.224.0/22 219.235.224.0/22
@@ -3982,9 +3992,6 @@
222.126.240.0/20 222.126.240.0/20
222.128.0.0/12 222.128.0.0/12
222.160.0.0/14 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.168.0.0/13
222.176.0.0/12 222.176.0.0/12
222.192.0.0/11 222.192.0.0/11
@@ -1 +1 @@
20260704060757 20260811040949
@@ -8,6 +8,7 @@
2001:daa:2::/47 2001:daa:2::/47
2001:daa:4::/47 2001:daa:4::/47
2001:daa:6::/48 2001:daa:6::/48
2001:daa:9::/48
2001:dc7::/32 2001:dc7::/32
2001:dd8:1::/48 2001:dd8:1::/48
2001:dd9::/48 2001:dd9::/48
@@ -57,12 +58,14 @@
2400:9380:90b1::/48 2400:9380:90b1::/48
2400:9380:90b2::/47 2400:9380:90b2::/47
2400:9380:90b4::/46 2400:9380:90b4::/46
2400:9380:9109::/48
2400:9380:9121::/48 2400:9380:9121::/48
2400:9380:9220::/47 2400:9380:9220::/47
2400:9380:9250::/47 2400:9380:9250::/47
2400:9380:9260::/48 2400:9380:9260::/48
2400:9380:9271::/48 2400:9380:9271::/48
2400:9380:9280::/47 2400:9380:9280::/47
2400:9380:9282::/48
2400:9380:92b1::/48 2400:9380:92b1::/48
2400:9380:92b2::/47 2400:9380:92b2::/47
2400:9380:92b4::/46 2400:9380:92b4::/46
@@ -131,6 +134,7 @@
2401:fa00:40::/48 2401:fa00:40::/48
2402:840:d000::/46 2402:840:d000::/46
2402:840:e000::/46 2402:840:e000::/46
2402:840:f000::/38
2402:c60::/48 2402:c60::/48
2402:1440::/32 2402:1440::/32
2402:2000::/32 2402:2000::/32
@@ -237,6 +241,8 @@
2404:7600::/32 2404:7600::/32
2404:7940::/32 2404:7940::/32
2404:c2c0::/40 2404:c2c0::/40
2404:c2c0:111::/48
2404:c2c0:112::/48
2404:c2c0:240::/44 2404:c2c0:240::/44
2404:c2c0:280::/44 2404:c2c0:280::/44
2404:c2c0:2c0::/44 2404:c2c0:2c0::/44
@@ -278,7 +284,6 @@
2405:e000::/32 2405:e000::/32
2405:f940::/32 2405:f940::/32
2406:280::/32 2406:280::/32
2406:840:9680::/44
2406:840:f889::/48 2406:840:f889::/48
2406:1080::/48 2406:1080::/48
2406:2700::/32 2406:2700::/32
@@ -463,7 +468,7 @@
2408:8406:a980::/42 2408:8406:a980::/42
2408:8406:b500::/41 2408:8406:b500::/41
2408:8406:b580::/42 2408:8406:b580::/42
2408:8407:500::/42 2408:8407:520::/43
2408:8409::/40 2408:8409::/40
2408:8409:100::/41 2408:8409:100::/41
2408:8409:1a0::/43 2408:8409:1a0::/43
@@ -807,6 +812,7 @@
2408:882c::/32 2408:882c::/32
2408:883a::/32 2408:883a::/32
2408:8844::/43 2408:8844::/43
2408:8844:40::/43
2408:8856::/31 2408:8856::/31
2408:8858::/30 2408:8858::/30
2408:8862::/31 2408:8862::/31
@@ -983,26 +989,25 @@
2a04:f580:9270::/48 2a04:f580:9270::/48
2a04:f580:9280::/48 2a04:f580:9280::/48
2a04:f580:9290::/48 2a04:f580:9290::/48
2a05:1086::/32
2a0a:d685:1ff::/48 2a0a:d685:1ff::/48
2a0a:d685:300::/40 2a0a:d685:300::/40
2a0b:4340:90::/48
2a0b:4340:97::/48 2a0b:4340:97::/48
2a0d:c7c7:400::/38 2a0d:c7c7:400::/38
2a0e:aa06:49f::/48 2a0e:aa06:49f::/48
2a0e:b107:740::/44 2a0e:b107:740::/44
2a0e:b107:2440::/44
2a0f:1cc5:1600::/44 2a0f:1cc5:1600::/44
2a0f:1cc5:4700::/40 2a0f:1cc5:4700::/40
2a0f:9400:6110::/48
2a12:cb41:600::/44
2a12:cb41:1200::/44 2a12:cb41:1200::/44
2a12:cb41:1300::/43 2a12:cb41:1300::/44
2a12:cb41:13f0::/44 2a12:cb41:13f0::/44
2a13:1801:180::/43 2a13:1801:180::/43
2a13:a5c3:f000::/40 2a13:a5c3:f000::/40
2a14:7580:d00::/43 2a14:7580:d10::/44
2a14:7580:d20::/46 2a14:7580:d20::/46
2a14:7580:d41::/48 2a14:7583:efe7::/48
2a14:7580:d42::/47
2a14:7580:dff::/48
2c0f:f7a8:8011::/48 2c0f:f7a8:8011::/48
2c0f:f7a8:8050::/48 2c0f:f7a8:8050::/48
2c0f:f7a8:805f::/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 173ng.com
18board.com 18board.com
18comic.org 18comic.org
18comic.vip
18mh.net 18mh.net
1984bbs.com 1984bbs.com
1991way.com 1991way.com
@@ -127,6 +128,7 @@ agoogleaday.com
agro.hk agro.hk
ai.dev ai.dev
ai.studio ai.studio
aihub.top
aiosearch.com aiosearch.com
aiph.net aiph.net
airconsole.com airconsole.com
@@ -294,7 +296,6 @@ atlanta168.com
atnext.com atnext.com
auctions.yahoo.co.jp auctions.yahoo.co.jp
audacy.com audacy.com
auntology.fandom.com
autodraw.com autodraw.com
av.movie av.movie
av01.tv av01.tv
@@ -342,6 +343,7 @@ baramangaonline.com
barnabu.co.uk barnabu.co.uk
bartender.dowjones.com bartender.dowjones.com
barton.de barton.de
basemaps.arcgis.com
bastillepost.com bastillepost.com
bayvoice.net bayvoice.net
bb-chat.tv bb-chat.tv
@@ -686,6 +688,7 @@ centauro.com.br
centerforhumanreprod.com centerforhumanreprod.com
centralnation.com centralnation.com
centurys.net centurys.net
cerebras.ai
certificate-transparency.org certificate-transparency.org
certificate.revocationcheck.com certificate.revocationcheck.com
cfr.org cfr.org
@@ -766,10 +769,12 @@ chrome.com
chromecast.com chromecast.com
chromeexperiments.com chromeexperiments.com
chromestatus.com chromestatus.com
cht.com.tw
ci-en.jp ci-en.jp
cia.gov cia.gov
cici.com cici.com
ciciai.com ciciai.com
circle19.org
cirosantilli.com cirosantilli.com
citizencn.com citizencn.com
citizenlab.ca citizenlab.ca
@@ -782,6 +787,7 @@ civilmedia.tw
civitai.com civitai.com
cixiaoya.club cixiaoya.club
ck101.com ck101.com
clashyun.com
classicalguitarblog.net classicalguitarblog.net
claude.ai claude.ai
claude.com claude.com
@@ -991,6 +997,7 @@ disneyplus.com
disp.cc disp.cc
disqus.com disqus.com
dit-inc.us dit-inc.us
divdyn.com
diyin.org diyin.org
dizhuzhishang.com dizhuzhishang.com
dl-laby.jp dl-laby.jp
@@ -1140,6 +1147,7 @@ eromanga-kingdom.com
eromangadouzin.com eromangadouzin.com
eromon.net eromon.net
esg.t91y.com esg.t91y.com
esim.redteago.com
eslite.com eslite.com
esurance.com esurance.com
etherdelta.com etherdelta.com
@@ -1162,6 +1170,7 @@ exmormon.org
expatshield.com expatshield.com
expecthim.com expecthim.com
expekt.com expekt.com
experience.arcgis.com
exploader.net exploader.net
expressnews.com expressnews.com
expressvpn.com expressvpn.com
@@ -1201,6 +1210,7 @@ falundafaindia.org
falunhr.org falunhr.org
famunion.com famunion.com
fanbox.cc fanbox.cc
fandom.com
fangeqiang.com fangeqiang.com
fanglizhi.info fanglizhi.info
fangong.org fangong.org
@@ -1892,7 +1902,6 @@ holyspiritspeaks.org
home.saxo home.saxo
homedepot.com homedepot.com
homeservershow.com homeservershow.com
hongkong.fandom.com
hongkongfp.com hongkongfp.com
hongzhi.li hongzhi.li
honven.xyz honven.xyz
@@ -2171,6 +2180,7 @@ kanshifang.com
kantie.org kantie.org
kaotic.com kaotic.com
karayou.com karayou.com
kards.com
kathmandupost.com kathmandupost.com
kawaiikawaii.jp kawaiikawaii.jp
kawase.com kawase.com
@@ -2255,6 +2265,7 @@ lenwhite.com
leonardo.ai leonardo.ai
lesoir.be lesoir.be
letscorp.net letscorp.net
level-plus.net
lexica.art lexica.art
lhakar.org lhakar.org
li.taipei li.taipei
@@ -2518,7 +2529,6 @@ mosavi.io
motherless.com motherless.com
movements.org movements.org
moviefap.com moviefap.com
mozilla.ai
mpettis.com mpettis.com
mpfinance.com mpfinance.com
mpinews.com mpinews.com
@@ -2673,6 +2683,7 @@ ntd.tv
ntdtv.com ntdtv.com
ntdtv.com.tw ntdtv.com.tw
ntdtv.cz ntdtv.cz
nthlink.com
nuexpo.com nuexpo.com
nurgo-software.com nurgo-software.com
nusatrip.com nusatrip.com
@@ -2987,7 +2998,6 @@ puffinbrowser.com
pullfolio.co pullfolio.co
pure18.com pure18.com
pureconcepts.net pureconcepts.net
puredns.org
pureinsight.org pureinsight.org
purepdf.com purepdf.com
purevpn.com purevpn.com
@@ -3032,7 +3042,6 @@ radio.co
radio.garden radio.garden
radioaustralia.net.au radioaustralia.net.au
radiohilight.net radiohilight.net
radiojar.com
radioline.co radioline.co
radiovaticana.org radiovaticana.org
radiovncr.com radiovncr.com
@@ -3145,7 +3154,6 @@ sadpanda.us
safechat.com safechat.com
safeguarddefenders.com safeguarddefenders.com
safervpn.com safervpn.com
sagernet.org
saintyculture.com saintyculture.com
sakuralive.com sakuralive.com
salvation.org.hk salvation.org.hk
@@ -3435,6 +3443,7 @@ synapse.org
syncback.com syncback.com
synergyse.com synergyse.com
syosetu.com syosetu.com
sysub.com
t-g.com t-g.com
t.co t.co
t.me t.me
@@ -3648,6 +3657,7 @@ to-porno.com
togetter.com togetter.com
token.im token.im
tokenlon.im tokenlon.im
tokenplus.app
tokyo-porn-tube.com tokyo-porn-tube.com
tokyocn.com tokyocn.com
tomp3.cc tomp3.cc
@@ -3689,6 +3699,7 @@ trtworld.com
truebuddha-md.org truebuddha-md.org
trustwallet.com trustwallet.com
truthsocial.com truthsocial.com
tryai.dev
tryheart.jp tryheart.jp
tsdr.uspto.gov tsdr.uspto.gov
tt1069.com tt1069.com
@@ -3870,6 +3881,7 @@ uyghurstudies.org
uyghurtribunal.com uyghurtribunal.com
uygur.fc2web.com uygur.fc2web.com
uymaarip.com uymaarip.com
v.recipes
v2.help v2.help
v2.hysteria.network v2.hysteria.network
v2ex.com v2ex.com
@@ -4135,7 +4147,6 @@ www.ajsands.com
www.antd.org www.antd.org
www.aolnews.com www.aolnews.com
www.bing.com www.bing.com
www.clashverge.dev
www.cmoinc.org www.cmoinc.org
www.dmm.com www.dmm.com
www.eastturkistan.net www.eastturkistan.net
@@ -4313,6 +4324,7 @@ zdnet.com.tw
zello.com zello.com
zenmate.com zenmate.com
zenmate.com.ru zenmate.com.ru
zenmux.ai
zerohedge.com zerohedge.com
zeronet.io zeronet.io
zh-hans.cfsh99.com zh-hans.cfsh99.com
@@ -1 +1 @@
202607052256 202608102230
@@ -13,7 +13,6 @@ import { connect } from 'ubus';
import { cursor } from 'uci'; import { cursor } from 'uci';
import { urldecode, urlencode } from 'luci.http'; import { urldecode, urlencode } from 'luci.http';
import { init_action } from 'luci.sys';
import { remove_subscription_nodes } from '/etc/homeproxy/scripts/node_references.uc'; import { remove_subscription_nodes } from '/etc/homeproxy/scripts/node_references.uc';
import { parse_surge_subscription } from '/etc/homeproxy/scripts/subscription_parsers.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)); version));
} }
function service_action(action) {
return system([ '/etc/init.d/homeproxy', action ]);
}
function parse_uri(uri) { function parse_uri(uri) {
let config, url, params; let config, url, params;
@@ -857,8 +860,8 @@ function main() {
if (need_restart) { if (need_restart) {
log('Restarting service...'); log('Restarting service...');
init_action('homeproxy', 'stop'); service_action('stop');
init_action('homeproxy', 'start'); service_action('start');
} }
log(sprintf('%s nodes added, %s updated, %s removed.', added, updated, removed)); log(sprintf('%s nodes added, %s updated, %s removed.', added, updated, removed));
@@ -885,8 +888,8 @@ if (!isEmpty(subscription_urls)) {
'请查看 HomeProxy 日志中的 [SUBSCRIBE] 记录并修复订阅配置'); '请查看 HomeProxy 日志中的 [SUBSCRIBE] 记录并修复订阅配置');
log('Restarting service...'); log('Restarting service...');
init_action('homeproxy', 'stop'); service_action('stop');
init_action('homeproxy', 'start'); service_action('start');
writeSubscriptionUpdateStatus(false, true, false, status_error); writeSubscriptionUpdateStatus(false, true, false, status_error);
} }
} else { } else {
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.8.12 PKG_VERSION:=26.8.12
PKG_RELEASE:=215 PKG_RELEASE:=216
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -20,19 +20,31 @@ function is_timehhmm(str) {
} }
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) { if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
cbi_validators['base64'] = function() {
return isBase64(this);
}
cbi_validators['json'] = function() { cbi_validators['json'] = function() {
return is_json(this); return is_json(this);
} }
cbi_validators['timehhmm'] = function() { cbi_validators['timehhmm'] = function() {
return is_timehhmm(this); return is_timehhmm(this);
} }
cbi_validators['uuid'] = function() {
return isUUID(this);
}
} else { } else {
L.require('validation').then(function(validation) { L.require('validation').then(function(validation) {
validation.types['base64'] = function() {
return this.assert(isBase64(this.value), _('Must be Base64 text!'));
}
validation.types['json'] = function() { validation.types['json'] = function() {
return this.assert(is_json(this.value), _('Must be JSON text!')); return this.assert(is_json(this.value), _('Must be JSON text!'));
} }
validation.types['timehhmm'] = function() { validation.types['timehhmm'] = function() {
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)')); 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; 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) { function decodeIfBase64(str) {
try { try {
let s = str.replace(/-/g, '+').replace(/_/g, '/'); let s = str.replace(/-/g, '+').replace(/_/g, '/');
@@ -24,6 +29,11 @@ function decodeIfBase64(str) {
return 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) { function waitForElement(selector, callback) {
const el = document.querySelector(selector); const el = document.querySelector(selector);
if (el) return callback(el); if (el) return callback(el);
@@ -57,12 +57,13 @@ function index()
--[[ Server ]] --[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true 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 ]] --[[ API ]]
entry({"admin", "services", appname, "server_update_config"}, call("server_update_config")).leaf = true 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_status"}, call("server_status")).leaf = true
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).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_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, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).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, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "reassign_group"}, call("reassign_group")).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, "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, "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, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "rollback_rules"}, call("rollback_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, "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, "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, "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]] --[[rule_list]]
entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true
@@ -797,19 +796,6 @@ function get_node()
http_write_json(result) http_write_json(result)
end 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() function reassign_group()
local ids = http.formvalue("ids") or "" local ids = http.formvalue("ids") or ""
local group = http.formvalue("group") or "default" local group = http.formvalue("group") or "default"
@@ -878,14 +864,14 @@ function server_update_config()
http_write_json_error() http_write_json_error()
end end
function server_user_status() function server_status()
local e = {} local e = {}
e.index = http.formvalue("index") 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 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) http_write_json(e)
end end
function server_user_log() function server_log()
local id = http.formvalue("id") local id = http.formvalue("id")
if fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then if fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall_server/" .. id .. ".log") 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) api.uci_save(uci, appname, true, true)
end end
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 api = require "luci.passwall.api"
local appname = "passwall" local appname = "passwall"
local sys = api.sys
local datatypes = api.datatypes
api.set_default_cbi() 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:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3] 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) return api.return_map(m)
@@ -146,7 +146,26 @@ end
s:append(Template(appname .. "/rule/rule_version")) s:append(Template(appname .. "/rule/rule_version"))
if has_xray or has_singbox then 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 end
return api.return_map(m) return api.return_map(m)
@@ -18,38 +18,39 @@ e.default = "90"
e.datatype = "range(0,600)" e.datatype = "range(0,600)"
e.rmempty = false e.rmempty = false
local cfgname = "user"
t = m:section(TypedSection, cfgname, translate("Users Manager")) s_server = m:section(TypedSection, "server", translate("Servers Manager"))
t.anonymous = true s_server.anonymous = true
t.addremove = true s_server.addremove = true
t.sortable = true s_server.sortable = true
t.template = "cbi/tblsection" s_server.template = "cbi/tblsection"
t.extedit = api.url("server_user", "%s") s_server.extedit = api.url("server_config", "%s")
function t.create(e, t) function s_server.create(self, section)
local uid = api.gen_random_char() local uid = api.gen_random_char()
TypedSection.create(e, uid) TypedSection.create(self, uid)
luci.http.redirect(e.extedit:format(uid)) luci.http.redirect(self.extedit:format(uid))
end end
function t.remove(e, t) function s_server.remove(self, section)
e.map.proceed = true local o = m:get(section) or {}
e.map:del(t) if o[".type"] == self.sectiontype then
luci.http.redirect(api.url("server")) m:del(section)
end
end end
e = t:option(Flag, "enable", translate("Enable")) e = s_server:option(Flag, "enable", translate("Enable"))
e.width = "5%" e.width = "5%"
e.rmempty = false e.rmempty = false
e = t:option(DummyValue, "status", translate("Status")) e = s_server:option(DummyValue, "status", translate("Status"))
e.rawhtml = true e.rawhtml = true
e.cfgvalue = function(t, n) 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 end
e = t:option(DummyValue, "remarks", translate("Remarks")) e = s_server:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%" e.width = "15%"
e = t:option(DummyValue, "type", translate("Type")) e = s_server:option(DummyValue, "type", translate("Type"))
e.width = "20%" e.width = "20%"
e.rawhtml = true e.rawhtml = true
e.cfgvalue = function(t, n) e.cfgvalue = function(t, n)
@@ -87,20 +88,74 @@ e.cfgvalue = function(t, n)
return str return str
end 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.default = "1"
e.rmempty = false e.rmempty = false
local sortable = Template(appname .. "/cbi/sortable") local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api sortable.api = api
sortable.appname = m.config 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(sortable)
m:append(Template(appname .. "/server/log")) m:append(Template(appname .. "/server/log"))
m:append(Template(appname .. "/server/users_list_status"))
return api.return_map(m) return api.return_map(m)
@@ -20,7 +20,12 @@ m:append(header)
m:append(Template(appname .. "/cbi/nodes_listvalue_com")) 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.addremove = false
s.dynamic = false s.dynamic = false
@@ -28,6 +28,12 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port" o.datatype = "port"
o:depends({ [_n("custom")] = false }) 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 = s:option(Flag, _n("realms"), translate("Realms"))
o.default = "0" o.default = "0"
o.rewrite_option = o.option 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.rewrite_option = o.option
o:depends({ [_n("realms")] = "1" }) 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 = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable")) o:value("", translate("Disable"))
o:value("salamander") o:value("salamander")
@@ -48,27 +48,18 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port" o.datatype = "port"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t) o = s:option(DynamicList, _n("users"), translate("User"))
if value and value == "1" then for i, v in ipairs(user_list) do
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or "" o:value(v[".name"], v.username)
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
end end
o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" }) o:depends({ [_n("protocol")] = "http" })
o:depends({ [_n("protocol")] = "socks" })
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")] = "shadowsocks" }) 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 = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
o:value("tcp", "TCP") 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 for a, t in ipairs(x_ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" }) 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 = s:option(ListValue, _n("ss_network"), translate("Transport"))
o.default = "tcp,udp" o.default = "tcp,udp"
o:value("tcp", "TCP") o:value("tcp", "TCP")
@@ -109,14 +103,6 @@ o.default = "1"
o.rmempty = false o.rmempty = false
o:depends({ [_n("protocol")] = "socks" }) 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 = s:option(ListValue, _n("flow"), translate("flow"))
o.default = "" o.default = ""
o:value("", translate("Disable")) 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.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" }) 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 = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable")) o:value("", translate("Disable"))
o:value("salamander") o:value("salamander")
@@ -60,35 +60,27 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port" o.datatype = "port"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("auth"), translate("Auth")) o = s:option(DynamicList, _n("users"), translate("User"))
o.validate = function(self, value, t) for i, v in ipairs(user_list) do
if value and value == "1" then o:value(v[".name"], v.username)
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
end end
o:depends({ [_n("protocol")] = "mixed" }) o:depends({ [_n("protocol")] = "mixed" })
o:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "http" }) 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")] = "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")] = "naive" })
o:depends({ [_n("protocol")] = "hysteria" })
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "anytls" }) o:depends({ [_n("protocol")] = "anytls" })
if singbox_tags:find("with_quic") then 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 = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
o.default = "100" o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria" })
@@ -97,20 +89,6 @@ if singbox_tags:find("with_quic") then
o.default = "100" o.default = "100"
o:depends({ [_n("protocol")] = "hysteria" }) 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 = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
o:depends({ [_n("protocol")] = "hysteria" }) 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.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ [_n("hysteria2_realms")] = "1" }) 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 = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable")) o:value("", translate("Disable"))
o:value("salamander") o:value("salamander")
@@ -233,14 +207,8 @@ o.rewrite_option = "method"
for a, t in ipairs(ss_method_list) do o:value(t) end for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password")) o = s:option(Value, _n("ss_password"), translate("Password"))
for i = 1, 3 do o:depends({ [_n("protocol")] = "shadowsocks" })
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(ListValue, _n("flow"), translate("flow")) o = s:option(ListValue, _n("flow"), translate("flow"))
o.default = "" o.default = ""
@@ -27,22 +27,11 @@ o = s:option(Value, _n("port"), translate("Listen Port"))
o.datatype = "port" o.datatype = "port"
o = s:option(Flag, _n("auth"), translate("Auth")) o = s:option(Flag, _n("auth"), translate("Auth"))
o.validate = function(self, value, t)
if value and value == "1" then o = s:option(ListValue, _n("user"), translate("User"))
local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or "" for i, v in ipairs(user_list) do
local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or "" o:value(v[".name"], v.username)
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
end 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:depends({ [_n("auth")] = true })
o = s:option(Flag, _n("log"), translate("Log")) 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.datatype = "port"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password")) o = s:option(ListValue, _n("user"), translate("User"))
o.password = true for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 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.datatype = "port"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(Value, _n("password"), translate("Password")) o = s:option(ListValue, _n("user"), translate("User"))
o.password = true for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 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 end
datatypes.timehhmm = is_timehhmm 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) 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 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 return false
@@ -102,29 +102,33 @@ local function start()
nft_file:write('flush chain inet fw4 PSW-SERVER\n') nft_file:write('flush chain inet fw4 PSW-SERVER\n')
nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n') nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n')
end end
uci:foreach(CONFIG, "user", function(user) uci:foreach(CONFIG, "server", function(server)
local id = user[".name"] local id = server[".name"]
local enable = user.enable local enable = server.enable
if enable and tonumber(enable) == 1 then if enable and tonumber(enable) == 1 then
local enable_log = user.log local enable_log = server.log
local log_path = nil local log_path = nil
if enable_log and enable_log == "1" then if enable_log and enable_log == "1" then
log_path = CONFIG_PATH .. "/" .. id .. ".log" log_path = CONFIG_PATH .. "/" .. id .. ".log"
else else
log_path = nil log_path = nil
end end
local remarks = user.remarks local remarks = server.remarks
local port = tonumber(user.port) local port = tonumber(server.port)
local bin local bin
local config = {} local config = {}
local config_file = CONFIG_PATH .. "/" .. id .. ".json" local config_file = CONFIG_PATH .. "/" .. id .. ".json"
local udp_forward = 1 local udp_forward = 1
local type = user.type or "" local type = server.type or ""
if type == "Socks" then if type == "Socks" then
local auth = "" local auth = ""
if user.auth and user.auth == "1" then if server.auth and server.auth == "1" then
local username = user.username or "" local user = nil
local password = user.password or "" 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 if username ~= "" and password ~= "" then
username = "-u " .. username username = "-u " .. username
password = "-P " .. password password = "-P " .. password
@@ -133,59 +137,59 @@ local function start()
end end
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path) bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
elseif type == "SSR" then elseif type == "SSR" then
if user.custom == "1" and user.config_str then if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(user.config_str)) config = jsonc.parse(api.base64Decode(server.config_str))
else else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user) config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end end
local udp_param = "" local udp_param = ""
udp_forward = tonumber(user.udp_forward) or 1 udp_forward = tonumber(server.udp_forward) or 1
if udp_forward == 1 then if udp_forward == 1 then
udp_param = "-u" udp_param = "-u"
end end
type = type:lower() type = type:lower()
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path) bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
elseif type == "SS-Rust" then elseif type == "SS-Rust" then
if user.custom == "1" and user.config_str then if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(user.config_str)) config = jsonc.parse(api.base64Decode(server.config_str))
else else
config = require(require_dir .. "util_shadowsocks").gen_config_server(user) config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end end
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path) bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
elseif type == "Xray" then elseif type == "Xray" then
if user.custom == "1" and user.config_str then if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(user.config_str)) config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then if log_path then
if not config.log then if not config.log then
config.log = {} config.log = {}
end end
config.log.loglevel = user.loglevel config.log.loglevel = server.loglevel
end end
else else
config = require(require_dir .. "util_xray").gen_config_server(user) config = require(require_dir .. "util_xray").gen_config_server(server)
end end
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path) bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
elseif type == "sing-box" then elseif type == "sing-box" then
if user.custom == "1" and user.config_str then if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(user.config_str)) config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then if log_path then
if not config.log then if not config.log then
config.log = {} config.log = {}
end end
config.log.timestamp = true config.log.timestamp = true
config.log.disabled = false config.log.disabled = false
config.log.level = user.loglevel config.log.level = server.loglevel
config.log.output = log_path config.log.output = log_path
end end
else else
config = require(require_dir .. "util_sing-box").gen_config_server(user) config = require(require_dir .. "util_sing-box").gen_config_server(server)
end end
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path) bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
elseif type == "Hysteria2" then elseif type == "Hysteria2" then
if user.custom == "1" and user.config_str then if server.custom == "1" and server.config_str then
config = jsonc.parse(api.base64Decode(user.config_str)) config = jsonc.parse(api.base64Decode(server.config_str))
else else
config = require(require_dir .. "util_hysteria2").gen_config_server(user) config = require(require_dir .. "util_hysteria2").gen_config_server(server)
end end
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path) bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
end end
@@ -203,7 +207,7 @@ local function start()
cmd(bin) cmd(bin)
end 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 bind_local and tonumber(bind_local) ~= 1 and port then
if nft_flag == "0" then if nft_flag == "0" then
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) 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 local jsonc = api.jsonc
function gen_config_server(node) 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 = { local config = {
listen = (function() listen = (function()
if node.hysteria2_realms and node.hysteria2_realm_url then if node.hysteria2_realms and node.hysteria2_realm_url then
@@ -25,10 +39,10 @@ function gen_config_server(node)
password = node.hysteria2_obfs_password password = node.hysteria2_obfs_password
} }
} or nil, } or nil,
auth = { auth = users and {
type = "password", type = "userpass",
password = node.hysteria2_auth_password userpass = users
}, } or nil,
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil, 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 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 local jsonc = api.jsonc
function gen_config_server(node) function gen_config_server(node)
local user = nil
if node.user then
user = uci:get_all("passwall2_server", node.user)
end
local config = {} local config = {}
config.server_port = tonumber(node.port) config.server_port = tonumber(node.port)
config.password = node.password config.password = user and user.password or ""
config.timeout = tonumber(node.timeout) config.timeout = tonumber(node.timeout)
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
config.method = node.method config.method = node.method
@@ -766,39 +766,71 @@ function gen_config_server(node)
listen_port = tonumber(node.port), 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 local protocol_table = nil
if node.protocol == "mixed" then if node.protocol == "mixed" then
protocol_table = { protocol_table = {
users = (node.auth == "1") and { users = users,
{
username = node.username,
password = node.password
}
} or nil,
set_system_proxy = false set_system_proxy = false
} }
end end
if node.protocol == "socks" then if node.protocol == "socks" then
protocol_table = { protocol_table = {
users = (node.auth == "1") and { users = users
{
username = node.username,
password = node.password
}
} or nil
} }
end end
if node.protocol == "http" then if node.protocol == "http" then
protocol_table = { protocol_table = {
users = (node.auth == "1") and { users = users,
{
username = node.username,
password = node.password
}
} or nil,
tls = (node.tls == "1") and tls or nil, tls = (node.tls == "1") and tls or nil,
} }
end end
@@ -806,21 +838,14 @@ function gen_config_server(node)
if node.protocol == "shadowsocks" then if node.protocol == "shadowsocks" then
protocol_table = { protocol_table = {
method = node.method, method = node.method,
password = node.password, password = node.ss_password,
users = users,
multiplex = mux, multiplex = mux,
} }
end end
if node.protocol == "vmess" then if node.protocol == "vmess" then
if node.uuid then if users then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
alterId = 0,
}
end
protocol_table = { protocol_table = {
users = users, users = users,
tls = (node.tls == "1") and tls or nil, tls = (node.tls == "1") and tls or nil,
@@ -831,15 +856,7 @@ function gen_config_server(node)
end end
if node.protocol == "vless" then if node.protocol == "vless" then
if node.uuid then if users then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
flow = node.flow,
}
end
protocol_table = { protocol_table = {
users = users, users = users,
tls = (node.tls == "1") and tls or nil, tls = (node.tls == "1") and tls or nil,
@@ -850,14 +867,7 @@ function gen_config_server(node)
end end
if node.protocol == "trojan" then if node.protocol == "trojan" then
if node.uuid then if users then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
password = node.uuid[i],
}
end
protocol_table = { protocol_table = {
users = users, users = users,
tls = (node.tls == "1") and tls or nil, tls = (node.tls == "1") and tls or nil,
@@ -870,15 +880,12 @@ function gen_config_server(node)
end end
if node.protocol == "naive" then if node.protocol == "naive" then
protocol_table = { if users then
users = { protocol_table = {
{ users = users,
username = node.username, tls = tls
password = node.password }
} end
},
tls = tls,
}
end end
if node.protocol == "hysteria" then if node.protocol == "hysteria" then
@@ -888,13 +895,7 @@ function gen_config_server(node)
up_mbps = tonumber(node.hysteria_up_mbps), up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps), down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs, obfs = node.hysteria_obfs,
users = { users = 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,
}
},
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_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 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 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 end
if node.protocol == "tuic" then if node.protocol == "tuic" then
if node.uuid then tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
local users = {} local alpn = {}
for i = 1, #node.uuid do string.gsub(node.tuic_alpn, '[^,]+', function(w)
users[i] = { table.insert(alpn, w)
name = node.uuid[i], end)
uuid = node.uuid[i], if #alpn > 0 then return alpn end
password = node.password return nil
} end)() or nil
end protocol_table = {
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function() users = users,
local alpn = {} congestion_control = node.tuic_congestion_control or "cubic",
string.gsub(node.tuic_alpn, '[^,]+', function(w) zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
table.insert(alpn, w) heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
end) tls = tls
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
end end
if node.protocol == "hysteria2" then if node.protocol == "hysteria2" then
@@ -953,12 +944,7 @@ function gen_config_server(node)
end end
return o return o
end)(node.hysteria2_obfs_type), end)(node.hysteria2_obfs_type),
users = { users = users,
{
name = "user1",
password = node.hysteria2_auth_password or nil,
}
},
ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false, ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
tls = tls, tls = tls,
realm = node.hysteria2_realms and (function() realm = node.hysteria2_realms and (function()
@@ -978,15 +964,12 @@ function gen_config_server(node)
end end
if node.protocol == "anytls" then if node.protocol == "anytls" then
protocol_table = { if users then
users = { protocol_table = {
{ users = users,
name = (node.username and node.username ~= "") and node.username or "sekai", tls = tls,
password = node.password }
} end
},
tls = tls,
}
end end
if node.protocol == "direct" then 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" } { protocol = "freedom", tag = "direct", settings = { finalRules = {{ action = "allow" }}}}, { protocol = "blackhole", tag = "blocked" }
} }
if node.protocol == "vmess" or node.protocol == "vless" then local users = node.users or {}
if node.uuid then local users = nil
local users = {} if node.users and #node.users > 0 then
for i = 1, #node.uuid do users = {}
users[i] = { for i, v in ipairs(node.users) do
id = node.uuid[i], local user = uci:get_all("passwall2_server", v) or {}
flow = (node.protocol == "vless" if user[".type"] == "user" then
and (node.tls == "1" or (node.decryption and node.decryption ~= "" and node.decryption ~= "none")) local u = {}
and node.flow and node.flow ~= "") and node.flow or nil 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
end
if #users == 0 then
users = nil
end
end
if node.protocol == "vmess" or node.protocol == "vless" then
if users then
settings = { settings = {
users = users, users = users,
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil 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 elseif node.protocol == "socks" then
settings = { settings = {
udp = ("1" == node.udp_forward) and true or false, udp = ("1" == node.udp_forward) and true or false,
auth = ("1" == node.auth) and "password" or "noauth", auth = users and "password" or "noauth",
users = ("1" == node.auth) and { users = users
{
user = node.username,
pass = node.password
}
} or nil
} }
elseif node.protocol == "http" then elseif node.protocol == "http" then
settings = { settings = {
allowTransparent = false, allowTransparent = false,
users = ("1" == node.auth) and { users = users
{
user = node.username,
pass = node.password
}
} or nil
} }
node.transport = "tcp" node.transport = "tcp"
node.tcp_guise = "none" node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then elseif node.protocol == "shadowsocks" then
settings = { settings = {
method = node.method, method = node.method,
password = node.password, password = node.ss_password,
network = node.ss_network or "TCP,UDP" users = users,
network = node.ss_network or "tcp,udp"
} }
elseif node.protocol == "trojan" then elseif node.protocol == "trojan" then
if node.uuid then if users then
local users = {}
for i = 1, #node.uuid do
users[i] = {
password = node.uuid[i],
}
end
settings = { settings = {
users = users users = users
} }
@@ -548,9 +562,7 @@ function gen_config_server(node)
elseif node.protocol == "hysteria2" then elseif node.protocol == "hysteria2" then
settings = { settings = {
version = 2, version = 2,
users = node.hysteria2_auth_password and { users = users
{ auth = node.hysteria2_auth_password }
}
} }
elseif node.protocol == "dokodemo-door" then elseif node.protocol == "dokodemo-door" then
settings = { settings = {
@@ -1,6 +1,7 @@
<% <%
local api = require "luci.passwall.api" local api = self.api
local appname = api.appname local appname = api.appname
local map = self.map
local is_js_luci = api.is_js_luci() local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset" 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)"); let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) { if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow); parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group); set_order(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";
}
}
} }
break; break;
case "del_all": case "del_all":
@@ -652,48 +645,24 @@ table td, .table .td {
return ids; return ids;
} }
function save_current_page_order(group) { function set_order(group) {
var table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table"); let table = document.getElementById("cbi-<%=appname%>-nodes-" + group + "-table");
if (!table) { if (!table) {
alert("<%:No table!%>"); alert("<%:No table!%>");
return; return;
} }
var rows = table.querySelectorAll("tr.cbi-section-table-row"); let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
alert("<%:No nodes!%>"); alert("<%:No nodes!%>");
return; return;
} }
var btn = document.getElementById("save_order_btn_" + group); let ids = [];
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
rows.forEach(function(row) { rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", ""); let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id); ids.push(id);
}); });
ajax.abortAll(); const input_order = document.getElementsByName(`group.order.${group}`)[0];
XHR.get('<%=api.url("save_node_order")%>', { input_order.value = ids.join(" ");
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!%>");
}
}
);
} }
//获取当前使用的节点 //获取当前使用的节点
@@ -947,16 +916,7 @@ table td, .table .td {
forceFallback: false, forceFallback: false,
swapThreshold: 0.65, swapThreshold: 0.65,
onEnd: function (evt) { onEnd: function (evt) {
//save_current_page_order(group); // 自动提交保存 set_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";
}
}
} }
}; };
try { try {
@@ -1302,9 +1262,9 @@ table td, .table .td {
</tr> </tr>
{{node-tr}} {{node-tr}}
</table> </table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create"> <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-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> </div>
</<%=section_tag%>> </<%=section_tag%>>
</script> </script>
@@ -1,7 +1,6 @@
<% <%
local api = require "luci.passwall.api" local api = self.api
local appname = api.appname local appname = api.appname
local uci = api.uci
local is_js_luci = api.is_js_luci() local is_js_luci = api.is_js_luci()
local section_tag = is_js_luci and "div" or "fieldset" local section_tag = is_js_luci and "div" or "fieldset"
local title_tag = is_js_luci and "h3" or "legend" 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)"); let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) { if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow); parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group); set_order(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";
}
}
} }
} }
@@ -214,48 +205,24 @@ table td, .table .td {
return ids; return ids;
} }
function save_current_page_order(group) { function set_order(group) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table"); let table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
if (!table) { if (!table) {
alert("<%:No table!%>"); alert("<%:No table!%>");
return; return;
} }
var rows = table.querySelectorAll("tr.cbi-section-table-row"); let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
alert("<%:No shunt_rules!%>"); alert("<%:No shunt_rules!%>");
return; return;
} }
var btn = document.getElementById("save_order_btn_" + group); let ids = [];
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
rows.forEach(function(row) { rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", ""); let id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id); ids.push(id);
}); });
XHR.get('<%=api.url("save_shunt_rule_order")%>', { const input_order = document.getElementsByName(`group.order.${group}`)[0];
group: group, input_order.value = ids.join(" ");
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!%>");
}
}
);
} }
// List drag and rearrange // List drag and rearrange
@@ -274,16 +241,7 @@ table td, .table .td {
forceFallback: false, forceFallback: false,
swapThreshold: 0.65, swapThreshold: 0.65,
onEnd: function (evt) { onEnd: function (evt) {
//save_current_page_order(group); // Auto save set_order(group);
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";
}
}
} }
}; };
try { try {
@@ -339,10 +297,10 @@ table td, .table .td {
</tr> </tr>
{{shunt_rule-tr}} {{shunt_rule-tr}}
</table> </table>
<input type="hidden" name="group.order.{{group}}">
<div class="cbi-section-create cbi-tblsection-create"> <div class="cbi-section-create cbi-tblsection-create">
<input type="text" id="add_name_{{group}}"> <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-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> </div>
</<%=section_tag%>> </<%=section_tag%>>
</script> </script>
@@ -4,7 +4,7 @@ local api = self.api
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
let node_id = "<%=self.section%>"; 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 () { document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=self.config%>"][name*="type"]', function(el) { 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" msgid "Add Mode"
msgstr "添加方式" msgstr "添加方式"
msgid "Save Order"
msgstr "保存当前顺序"
msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。"
msgid "Drag to reorder" msgid "Drag to reorder"
msgstr "拖动以重排" msgstr "拖动以重排"
@@ -1670,9 +1664,15 @@ msgstr "服务器端"
msgid "Server Config" msgid "Server Config"
msgstr "服务器配置" msgstr "服务器配置"
msgid "Servers Manager"
msgstr "服务器管理"
msgid "Users Manager" msgid "Users Manager"
msgstr "用户管理" msgstr "用户管理"
msgid "User Config"
msgstr "用户配置"
msgid "Logs" msgid "Logs"
msgstr "日志" msgstr "日志"
@@ -2109,9 +2109,15 @@ msgstr "使用自定义配置"
msgid "Custom Config" msgid "Custom Config"
msgstr "自定义配置" msgstr "自定义配置"
msgid "Must be Base64 text!"
msgstr "必须是 Base64 内容!"
msgid "Must be JSON text!" msgid "Must be JSON text!"
msgstr "必须是 JSON 文本内容!" msgstr "必须是 JSON 文本内容!"
msgid "Must be UUID format!"
msgstr "必须是 UUID 格式!"
msgid "Geo View" msgid "Geo View"
msgstr "Geo 查询" msgstr "Geo 查询"
+1 -1
View File
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?= FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),) ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION) PKG_VERSION:=$(FOOTSTRAP_VERSION)
PKG_RELEASE:=15 PKG_RELEASE:=16
endif endif
LUCI_TITLE:=Footstrap Theme LUCI_TITLE:=Footstrap Theme
@@ -684,9 +684,9 @@ function takeReturn() {
return true; return true;
} catch (e) { return false; } } 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 */ /* 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 */ 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; let _routeObserver = null, _viewObserver = null, _observedView = null, _building = false;
@@ -23,8 +23,18 @@ const _fitters = [];
let _rafPending = false; let _rafPending = false;
let _ro = null, _mo = null; let _ro = null, _mo = null;
/* one canvas for the whole document: wordFloor() measures text with it, and creating one per call /* 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; 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+/; const SPACE_RE = /\s+/;
/* Run every fitter NOW, synchronously. A fitter must be idempotent this fires on every /* Run every fitter NOW, synchronously. A fitter must be idempotent this fires on every
@@ -155,7 +165,15 @@ return baseclass.extend({
wordFloor(t) { wordFloor(t) {
const rows = t.querySelectorAll('.tr:not(.table-titles):not(.cbi-section-table-titles):not(.placeholder)'); const rows = t.querySelectorAll('.tr:not(.table-titles):not(.cbi-section-table-titles):not(.placeholder)');
if (!rows.length) return 0; 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 = []; const floors = [], longest = [];
let cols = null; let cols = null;
for (const row of rows) { 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; else for (const w of text.split(SPACE_RE)) if (w.length > word.length) word = w;
if (!(word.length > (longest[i] || 0))) continue; if (!(word.length > (longest[i] || 0))) continue;
longest[i] = word.length; longest[i] = word.length;
_cx.font = col.font; cx.font = col.font;
const need = _cx.measureText(word).width + (col.tracking * word.length) + col.pad; const need = cx.measureText(word).width + (col.tracking * word.length) + col.pad;
if (!(floors[i] >= need)) floors[i] = need; 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. * 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 * 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. */ * 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); const running = (typeof L.Poll.active === 'function') ? L.Poll.active() : (L.Poll.timer != null);
if (running && L.Poll.timer == null) { if (running && L.Poll.timer == null) {
if (!_pollTimerWarned) { warnPollUnreadable('footstrap: LuCI is polling but L.Poll.timer is not readable — leaving view '
_pollTimerWarned = true; + 'intervals alone rather than risking its tick. fs-router.js needs updating for this '
console.error('footstrap: LuCI is polling but L.Poll.timer is not readable — leaving view ' + 'luci-base.');
+ 'intervals alone rather than risking its tick. fs-router.js needs updating for this '
+ 'luci-base.');
}
return; return;
} }
const keep = running ? L.Poll.timer : null; const keep = running ? L.Poll.timer : null;
_viewIntervals.forEach((id) => { if (id !== keep) window.clearInterval(id); }); _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; let _pollTimerWarned = false;
function warnPollUnreadable(msg) {
if (_pollTimerWarned) return;
_pollTimerWarned = true;
console.error(msg);
}
/* --- uci cache teardown for SPA nav --- /* --- uci cache teardown for SPA nav ---
* `uci.load()` does not answer "is this config present?" it answers "which of these packages did * `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. */ * repairStaleRender() a mess that only exists because a require in flight cannot be stopped. */
if (gen !== _navGen) return null; if (gen !== _navGen) return null;
_seen.add(className); _seen.add(className);
/* NAME THE OWNER for the length of this require. On a first visit the require IS the render, /* NAME THE OWNER for the length of this require, AND ONLY WHEN THE MODULE HAS YET TO BE
* so any <style> the module injects belongs to THIS page even if a newer navigation has * EVALUATED. On a first visit the require IS the render, so any <style> the module injects
* stamped data-page by the time it lands, which is exactly the case repairStaleRender() below * belongs to THIS page even if a newer navigation has stamped data-page by the time it
* exists for. Without it fs-sheets credited such a sheet to the page that superseded this one * lands, which is exactly the case repairStaleRender() below exists for. Without it fs-sheets
* and bound it there for the life of the document (measured: luci-app-filemanager's * credited such a sheet to the page that superseded this one and bound it there for the life
* `.cbi-button-save { display: none !important }` disabled on its own page and live on * of the document (measured: luci-app-filemanager's `.cbi-button-save { display: none
* System -> System, across return visits). */ * !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)); * `!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) => { }).then((view) => {
if (view == null) return; if (view == null) return;
if (!(view instanceof RT.view)) if (!(view instanceof RT.view))
@@ -185,8 +185,21 @@ function enhance(sel) {
* openclash / justclash / ssclash / dashboard / statistics included, found ZERO. That is the point: * 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 * 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). */ * 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), ' + /* NO `:not(.fs-dt)`, and that is the difference between claiming a table once and KEEPING it. These
'#view table:not(.table):not(.cbi-section-table):not(.fs-dt)'; * 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 /* 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 * `<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` * 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. */ * the two names theme/30-tables.css hides when stacked. */
function adoptMarkup(t, head) { function adoptMarkup(t, head) {
/* DECIDED ONCE, AT CLAIM TIME, and only for a table that speaks none of this vocabulary. Asking /* DECIDED ONCE, AT CLAIM TIME, and only for a table that speaks none of this vocabulary then
* the question every pass instead would answer "already adopted" the moment we adopted it and * READ on every pass, because the caller now revisits a table it has already claimed (see
* these tables are polled, so the fresh rows that arrive bare afterwards would never be taken. * 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 * 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 * 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. */ * 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). * 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 * 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 * that hint.
* second navigation supersedes the first before it starts one. */ *
* 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; 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); _ownerHint = (segs == null) ? null : appKey(segs);
_ownerGen = (segs == null) ? -1 : gen;
} }
function ownerKey() { 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) { function rehostIntoThemeLayer(el, universe) {
if (el.dataset.fsLayered) return; if (el.dataset.fsLayered) return;
@@ -735,7 +773,7 @@ function rehostIntoThemeLayer(el, universe) {
el.dataset.fsLayered = '1'; el.dataset.fsLayered = '1';
el.after(s); /* keep source order: ties inside the layer still resolve as they did */ el.after(s); /* keep source order: ties inside the layer still resolve as they did */
silence(el); 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 */ fenceImported(s, universe.names, Date.now() + 1000); /* a cache hit lands on the first frame */
return; 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 * 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. */ * that says the work is done, so it has to be set for every path below, wrapped or not. */
el.dataset.fsLayered = '1'; 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 /* 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, * 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}'; el.textContent = '@layer theme {\n' + el.textContent + '\n}';
} }
try { if (el.sheet) fenceRules(el.sheet.cssRules, universe.names); } catch (e) { /* unfenced, not broken */ } 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 /* 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); --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 /* 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 * 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 * 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_NAME:=natflow
PKG_VERSION:=20260531 PKG_VERSION:=20260531
PKG_RELEASE:=64 PKG_RELEASE:=65
PKG_SOURCE:=$(PKG_VERSION).tar.xz PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=b3e749ae53becc4d31596e40d18e4875e518adc1 PKG_SOURCE_VERSION:=07142a20690b250747bcd0ded13586a5ee5a8020
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com> PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0 PKG_LICENSE:=GPL-2.0
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=nexttrace PKG_NAME:=nexttrace
PKG_VERSION:=1.7.1 PKG_VERSION:=1.7.2
PKG_RELEASE:=12 PKG_RELEASE:=13
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?