Compare commits

...

215 Commits
1.8.4 ... main

Author SHA1 Message Date
Alireza Ahmadi
5d14f381c2 v1.10.2 2026-02-27 02:11:50 +01:00
Alireza Ahmadi
088dd2e881 fix session maxAge #1625 2026-02-27 01:42:33 +01:00
Alireza Ahmadi
5909955f5d remove test from #1622 2026-02-27 00:40:47 +01:00
Davood Qorbani
a3df597b5d return TCPing test + External proxy should no longer show : null (#1621)
When Subscription Json is enabled in Alireza x-ui +v1.10.0
- TCPing test in apps such as v2rayNG should return again
- External proxy should no longer show ": null"
also:
- Preserves VMess compatibility properly
- Keeps level consistent
- Avoids empty flow
- Matches standard Xray schema precisely
2026-02-27 00:39:20 +01:00
Alireza Ahmadi
49e43a2079 Merge pull request #1622 from shayan775/fix-AddClientTraffic
The too many SQL variables path is now batched.
2026-02-27 00:37:27 +01:00
Alireza Ahmadi
95afd3006a improve expiration delay start 2026-02-27 00:30:50 +01:00
shayan775
bcea56283f The too many SQL variables path is now batched.
Changed code:

Added safe batching limits in inbound.go:
safeSQLVariablesPerQuery = 900
safeSaveBatchSize = 50
Fixed addClientTraffic in inbound.go:
email IN (...) lookup is now chunked (line 815).
tx.Save(dbClientTraffics) is now chunked (line 859).
Added nil guard for p.SetOnlineClients(...) (line 855).
Hardened adjustTraffics in inbound.go:
deduplicates inbound IDs before querying (line 877).
chunked inbound IN query (line 890).
chunked inbound Save (line 932).
Added regression test:

inbound_add_client_traffic_test.go
Verifies 4,000 client traffic updates succeed and totals are correct.
Validation run:

go test ./web/service -run TestAddClientTrafficHandlesLargeBatch -count=1 passed
go test ./web/service -count=1 passed
2026-02-24 19:19:04 +03:30
Alireza Ahmadi
e70fb3daf5 Xray-Core v26.2.6 2026-02-20 02:11:24 +01:00
Alireza Ahmadi
322a74429d fix windows signal 2026-02-20 02:09:06 +01:00
Alireza Ahmadi
5b6daf9e70 Go 1.25.7 2026-02-20 01:16:03 +01:00
Alireza Ahmadi
e5184b81be sub json fix fragment noises effect #1617 2026-02-20 01:05:37 +01:00
Alireza Ahmadi
35798c2c74 fix prompt #1618 2026-02-20 00:14:07 +01:00
Alireza Ahmadi
6781b0f7ae add xray-core restart option in cli 2026-02-20 00:08:36 +01:00
Alireza Ahmadi
9d93468332 v1.10.1 2026-02-07 15:51:08 +01:00
Alireza Ahmadi
5e28644ec7 Merge pull request #1615 from sigseg5/main
Add CONTRIBUTING.md guideline with Docker and local development setup
2026-02-07 15:32:12 +01:00
sigseg5
9b08f181a6 add contributing guideline with docker and local developer setup 2026-02-06 22:42:51 +03:00
Alireza Ahmadi
1a0c39693e fix: trim whitespace
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-03 20:54:42 +01:00
Alireza Ahmadi
18662b9c71 xray-core v26.2.2 2026-02-03 20:50:20 +01:00
Alireza Ahmadi
c39ad9cac1 Finalmask: Add XICMP
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-03 20:48:08 +01:00
Alireza Ahmadi
b8f2195a3a fix timelocation for windows 2026-02-02 16:41:51 +01:00
Alireza Ahmadi
57d437dff8 corrections 2026-02-02 16:22:21 +01:00
Alireza Ahmadi
adead1cc39 v1.10.0 2026-02-01 13:20:17 +01:00
Alireza Ahmadi
41e4bb974b noKernelTun instead of kernelMode #1597 2026-02-01 13:04:44 +01:00
Alireza Ahmadi
7dbb36a5d5 Merge pull request #1611 from sigseg5/main
Address #1609 issue – added cron/cronie package install at dependency installation step
2026-02-01 12:47:00 +01:00
Alireza Ahmadi
5b8ded95d2 hop interval min-max
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-01 12:44:16 +01:00
Alireza Ahmadi
1904a7b85c better finalmask 2026-02-01 12:43:46 +01:00
Alireza Ahmadi
94c4becb34 Add pinnedPeerCertSha256 support to TLS settings
Introduces the pinnedPeerCertSha256 field to TlsStreamSettings in the JS model and adds a corresponding input in the TLS settings form. This allows users to specify SHA256 fingerprints for peer certificate pinning, enhancing security configuration options.

Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-01 10:51:38 +01:00
Alireza Ahmadi
8ba7f73736 XHTTP transport: New options for bypassing CDN's detection
https://github.com/XTLS/Xray-core/pull/5414

Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-01 10:50:55 +01:00
Alireza Ahmadi
8f5bead445 finalmask
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-01 10:32:35 +01:00
Alireza Ahmadi
6bab6ce6c4 Updates so far
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-02-01 00:51:53 +01:00
sigseg5
e835509acc add cron/cronie install as dep 2026-01-09 00:50:07 +03:00
Alireza Ahmadi
d1606d1109 new donation link 2025-09-19 00:12:51 +02:00
Alireza Ahmadi
2ae72aa8d1 v1.9.1 2025-09-18 23:33:13 +02:00
Alireza Ahmadi
1b0828eb3e Merge pull request #1596 from alireza0/test
go package correction
2025-09-18 22:45:53 +02:00
Alireza Ahmadi
b6252151f4 go package correction 2025-09-18 22:33:02 +02:00
Alireza Ahmadi
832d561d52 fix xray filename in arm docker image #1587 2025-09-18 00:32:49 +02:00
Alireza Ahmadi
d7fd3e2109 [api] add server endpoint #1590 2025-09-17 23:23:16 +02:00
Alireza Ahmadi
430ade7952 v1.9.0 2025-09-14 23:15:05 +02:00
Alireza Ahmadi
47ee9b503c fix windows readme 2025-09-14 22:39:16 +02:00
Alireza Ahmadi
4c98c241ed fix windows db folder 2025-09-14 22:34:11 +02:00
Alireza Ahmadi
aa5ec7a343 Merge pull request #1594 from MHSanaei/main
update pack
2025-09-14 22:29:49 +02:00
mhsanaei
7d9f01a621 update pack 2025-09-13 15:09:02 +02:00
Alireza Ahmadi
5d07744c41 update readme 2025-08-10 13:29:14 +02:00
Alireza Ahmadi
af54cca281 fix saving sockopt 2025-08-09 16:07:08 +02:00
Alireza Ahmadi
f2d4af1861 better musl libc usage 2025-08-08 19:49:27 +02:00
Alireza Ahmadi
a3977ef6a1 v1.8.12 2025-08-06 11:11:28 +02:00
Alireza Ahmadi
3af4805eb6 add sockopt to dockodemo 2025-08-05 14:05:55 +02:00
Alireza Ahmadi
1ceba486ce full sockopt 2025-08-05 14:05:41 +02:00
Alireza Ahmadi
49990d7841 remove os version dependency check 2025-08-04 21:51:06 +02:00
Alireza Ahmadi
eb93fecdd1 v1.8.11 2025-08-04 19:35:38 +02:00
Alireza Ahmadi
8e992ff921 Merge pull request #1586 from MHSanaei/main
update
2025-08-04 17:16:08 +02:00
Alireza Ahmadi
0456ed702e Merge branch 'main' into main 2025-08-04 17:15:41 +02:00
Alireza Ahmadi
b11dc56cd0 add dokodemo port map 2025-08-04 17:14:55 +02:00
mhsanaei
2d60543026 use musl libc toolchains
Co-Authored-By: elseif <253712+elseif@users.noreply.github.com>
2025-08-04 16:55:52 +02:00
Sanaei
221dda5a1a Merge branch 'alireza0:main' into main 2025-08-04 16:51:21 +02:00
mhsanaei
b50f7dd91a inbound: pqv 2025-08-04 16:48:13 +02:00
mhsanaei
e752ad75be outbound: mldsa65Verify 2025-08-04 16:47:31 +02:00
Alireza Ahmadi
0ce3d1ad97 add dokodemo port map 2025-08-04 16:45:21 +02:00
Alireza Ahmadi
14b926582f add ech support 2025-08-04 15:56:24 +02:00
Alireza Ahmadi
be76dc993a update packages 2025-08-04 14:28:45 +02:00
Alireza Ahmadi
2d659904d0 xray-core v25.8.3 2025-08-04 14:24:16 +02:00
Alireza Ahmadi
04e84386fc v1.8.10 2025-08-02 11:52:08 +02:00
Alireza Ahmadi
834e03cf4e v1.8.10 2025-07-30 23:10:02 +02:00
Alireza Ahmadi
244c394a6d v1.8.10-beta.1 2025-07-30 16:39:28 +02:00
Alireza Ahmadi
58c58e5fbd go 1.24.5 and other updates 2025-07-30 16:38:33 +02:00
Alireza Ahmadi
e954ccd957 so-far updates 2025-07-30 16:09:15 +02:00
Alireza Ahmadi
ee03836eeb quiet build 2025-01-27 00:52:31 +01:00
Alireza Ahmadi
44d34c8090 v1.8.9 2025-01-27 00:01:50 +01:00
Alireza Ahmadi
2cf66eb726 [subJson] better direct options #1528 2025-01-26 14:32:04 +01:00
Alireza Ahmadi
e9ea435d89 Merge pull request #1537 from MHSanaei/main
update pack
2025-01-22 01:42:28 +01:00
Alireza Ahmadi
526a5ab8b4 Merge branch 'main' into main 2025-01-22 01:42:20 +01:00
Alireza Ahmadi
2c83daef47 Merge pull request #1542 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.69.4
Bump google.golang.org/grpc from 1.68.0 to 1.69.4
2025-01-22 01:37:47 +01:00
Alireza Ahmadi
7c992f58af Merge pull request #1538 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.12
Bump github.com/shirou/gopsutil/v4 from 4.24.10 to 4.24.12
2025-01-22 01:37:34 +01:00
dependabot[bot]
94978babda Bump google.golang.org/grpc from 1.68.0 to 1.69.4
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.68.0 to 1.69.4.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.68.0...v1.69.4)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 00:36:47 +00:00
dependabot[bot]
bd00bb5e7b Bump github.com/shirou/gopsutil/v4 from 4.24.10 to 4.24.12
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.10 to 4.24.12.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.10...v4.24.12)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 00:36:47 +00:00
Alireza Ahmadi
0f1be9bfa1 Merge pull request #1527 from alireza0/dependabot/go_modules/github.com/goccy/go-json-0.10.4
Bump github.com/goccy/go-json from 0.10.3 to 0.10.4
2025-01-22 01:35:53 +01:00
Alireza Ahmadi
ed52cd16d4 Merge pull request #1525 from alireza0/dependabot/go_modules/gorm.io/driver/sqlite-1.5.7
Bump gorm.io/driver/sqlite from 1.5.6 to 1.5.7
2025-01-22 01:35:42 +01:00
Alireza Ahmadi
49cc3f4c26 Merge pull request #1521 from alireza0/dependabot/go_modules/golang.org/x/text-0.21.0
Bump golang.org/x/text from 0.20.0 to 0.21.0
2025-01-22 01:35:31 +01:00
Alireza Ahmadi
08e7419a69 Merge pull request #1523 from dionisvl/patch-1
Update docker-compose.yml
2025-01-22 01:35:19 +01:00
mhsanaei
7058a2bd38 minor changes 2025-01-05 21:04:08 +01:00
mhsanaei
ae37131b4b tcpNoDelay to penetrate 2025-01-01 18:57:56 +01:00
mhsanaei
33d7154a67 xmux - hMaxReusableSecs 2025-01-01 18:52:35 +01:00
mhsanaei
c540a0abc2 Xray Core v25.1.1 2025-01-01 18:26:23 +01:00
MHSanaei
ecd9ae5f4a bug fix - outbound xhttp link 2024-12-27 21:32:47 +01:00
MHSanaei
1d90dea014 Delete dependabot.yml 2024-12-27 21:17:36 +01:00
MHSanaei
274a753a4c Transport: Remove HTTP 2024-12-27 21:12:20 +01:00
MHSanaei
b50592ec14 TLS, REALITY : fingerprint set default to chrome 2024-12-27 21:04:37 +01:00
MHSanaei
d1a178d483 splithttp to xhttp 2024-12-27 21:03:00 +01:00
MHSanaei
dd49e89b2e UTLS: unsafe 2024-12-27 20:52:30 +01:00
MHSanaei
fd3242ad31 Xray Core v24.12.18 2024-12-27 20:51:03 +01:00
MHSanaei
8bdb45aabc axios v1.7.9 2024-12-27 20:49:54 +01:00
MHSanaei
ae38f3e2ab moment v2.30.1 2024-12-27 20:49:43 +01:00
MHSanaei
9dd76a4bff update dependencies 2024-12-27 20:46:06 +01:00
dependabot[bot]
0610a6f5c3 Bump github.com/goccy/go-json from 0.10.3 to 0.10.4
Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/goccy/go-json/releases)
- [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-json/compare/v0.10.3...v0.10.4)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-13 21:48:08 +00:00
dependabot[bot]
4010ff6a77 Bump gorm.io/driver/sqlite from 1.5.6 to 1.5.7
Bumps [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite) from 1.5.6 to 1.5.7.
- [Commits](https://github.com/go-gorm/sqlite/compare/v1.5.6...v1.5.7)

---
updated-dependencies:
- dependency-name: gorm.io/driver/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 21:49:34 +00:00
Denis
e3be646c77 Update docker-compose.yml
docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
2024-12-05 23:45:27 +03:00
dependabot[bot]
cedb8ea2ac Bump golang.org/x/text from 0.20.0 to 0.21.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-04 21:58:19 +00:00
Alireza Ahmadi
3a7c00fc5f fix crash report 2024-11-25 22:50:50 +01:00
Alireza Ahmadi
f2329c20df [warp] report error in change license 2024-11-23 19:47:19 +01:00
Alireza Ahmadi
275fc4564d fix typo 2024-11-16 18:05:37 +01:00
Alireza Ahmadi
d28231b0f8 v1.8.8 2024-11-16 17:58:09 +01:00
Alireza Ahmadi
5403c50c11 show URI in scripts 2024-11-16 17:36:28 +01:00
Alireza Ahmadi
70472ce0af Xray v24.11.11 2024-11-16 17:35:52 +01:00
Alireza Ahmadi
53de73dd5a Change xray.js to inbound.js 2024-11-16 17:14:08 +01:00
Alireza Ahmadi
c75a4509cd Core crash report 2024-11-16 16:51:46 +01:00
Alireza Ahmadi
5260ea91ac Enable reality for splithttp 2024-11-16 16:51:15 +01:00
Alireza Ahmadi
44d415f5f7 [cmd] get panel uri 2024-11-16 15:54:47 +01:00
Alireza Ahmadi
a27ca6d872 fix typo 2024-11-16 15:54:23 +01:00
Alireza Ahmadi
288a560687 ShadowSocks - ivCheck
Co-Authored-By: MHSanaei <ho3ein.sanaei@gmail.com>
2024-11-16 15:54:00 +01:00
Alireza Ahmadi
2f61bbcfdd SplitHTTP - Mode
Co-Authored-By: MHSanaei <ho3ein.sanaei@gmail.com>
2024-11-16 15:42:20 +01:00
dependabot[bot]
31cec0280a Bump golang.org/x/text from 0.19.0 to 0.20.0 (#1504)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2024-11-14 21:02:03 +01:00
Alireza Ahmadi
3b79e53d3c Merge pull request #1503 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.68.0
Bump google.golang.org/grpc from 1.67.1 to 1.68.0
2024-11-14 21:01:19 +01:00
Alireza Ahmadi
26fe3e19a3 Merge pull request #1500 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.10
Bump github.com/shirou/gopsutil/v4 from 4.24.9 to 4.24.10
2024-11-14 21:01:08 +01:00
dependabot[bot]
7933da84c9 Bump google.golang.org/grpc from 1.67.1 to 1.68.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.1 to 1.68.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.1...v1.68.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-07 21:17:35 +00:00
Alireza Ahmadi
088eb6dd94 get host from header in proxy call 2024-11-02 13:14:07 +01:00
dependabot[bot]
248c63296c Bump github.com/shirou/gopsutil/v4 from 4.24.9 to 4.24.10
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.9 to 4.24.10.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.9...v4.24.10)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 21:14:32 +00:00
Sanaei
ebf45c2131 Update install.sh (#1495)
* Update install.sh

* legacy version
2024-10-28 16:44:22 +01:00
dependabot[bot]
d1725dbc23 Bump github.com/nicksnyder/go-i18n/v2 from 2.4.0 to 2.4.1 (#1487)
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2024-10-20 15:25:37 +02:00
Alireza Ahmadi
de11119ed6 Merge pull request #1489 from MHSanaei/main
update
2024-10-20 15:24:29 +02:00
Alireza Ahmadi
d75b4d3d9e fix outbound noises 2024-10-20 14:51:43 +02:00
Alireza Ahmadi
aae79a44f8 fix old version install command 2024-10-20 14:08:52 +02:00
mhsanaei
36930b1bfd Calidity to gin-contrib 2024-10-17 13:58:29 +02:00
mhsanaei
81086de306 install.sh - check existing settings and random username & password 2024-10-17 13:13:14 +02:00
mhsanaei
1b9486784d bash menu - debug log, clear all logs 2024-10-17 13:09:55 +02:00
mhsanaei
1e61803add 500 rows for log page 2024-10-17 13:08:49 +02:00
mhsanaei
fe3ac97334 splithttp - xmux (change default value) 2024-10-17 13:07:27 +02:00
mhsanaei
d5e19ce077 update config 2024-10-17 13:06:00 +02:00
Alireza Ahmadi
ce49394284 fix core restart on traffic reset of disabled client 2024-10-13 15:05:29 +02:00
Alireza Ahmadi
50dba7b5b7 update README: using old versions 2024-10-10 23:10:17 +02:00
Alireza Ahmadi
e6bcb7534d fix subJson direct rules #1477 2024-10-10 22:13:29 +02:00
Alireza Ahmadi
68f108040a remove unused translation parts 2024-10-10 19:55:08 +02:00
Alireza Ahmadi
8ff38d603e v1.8.7 2024-10-10 16:25:53 +02:00
Alireza Ahmadi
56c5b0d507 [bug] fix restarting core on disabling depleted user 2024-10-10 16:10:21 +02:00
Alireza Ahmadi
fdd3df788e Merge pull request #1473 from MHSanaei/main
new features
2024-10-10 15:25:18 +02:00
mhsanaei
5be4188b5c Sponsor this project 2024-10-09 11:59:32 +02:00
mhsanaei
582a7493b7 Log - maskAddress , dnslog 2024-10-09 11:56:37 +02:00
mhsanaei
1432676c4d DNS Outbound - nonIPQuery, blockTypes 2024-10-09 11:47:46 +02:00
mhsanaei
432f3570bf HTTP - Allow Transparent 2024-10-09 11:43:44 +02:00
mhsanaei
c6c43b9b47 DNS - Expect IPs 2024-10-09 11:41:15 +02:00
Alireza Ahmadi
3582876b82 [xray] simplified routing 2024-10-09 00:56:54 +02:00
Alireza Ahmadi
7f00385541 fix typo 2024-10-09 00:44:15 +02:00
Alireza Ahmadi
b2465ae37f go version from mod file 2024-10-09 00:43:58 +02:00
Alireza Ahmadi
288a40d982 Merge pull request #1470 from MHSanaei/main
new changes
2024-10-08 21:26:16 +02:00
mhsanaei
b06666c976 update block, direct connections 2024-10-08 17:01:48 +02:00
mhsanaei
2b38c2abd1 fix oracle 2024-10-08 16:43:06 +02:00
mhsanaei
5c5c0d79cf inbound - better view 2024-10-08 16:26:13 +02:00
mhsanaei
882a834388 refactor: split Reality form tls into separate files 2024-10-08 16:22:55 +02:00
mhsanaei
ec2d8ccc86 noises, more option for direct json 2024-10-08 16:17:37 +02:00
mhsanaei
9b00297a4b remove timeout 2024-10-08 15:38:36 +02:00
mhsanaei
b27199d307 remove quic 2024-10-08 15:35:17 +02:00
mhsanaei
123b8828ab Xray Core v24.9.30 2024-10-08 15:27:46 +02:00
mhsanaei
1264831dc0 update dependencies 2024-10-08 15:26:19 +02:00
mhsanaei
e9504b559e xmux - splithttp 2024-10-08 15:18:03 +02:00
mhsanaei
f0f39090b6 more os support 2024-10-08 15:15:43 +02:00
mhsanaei
f758e75a84 random path - set cert to panel
random port
make random path if empty
Show Existing Domains
set cert to panel
2024-10-08 15:10:03 +02:00
dependabot[bot]
a75f4c03a5 Bump golang.org/x/text from 0.18.0 to 0.19.0 (#1467)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 23:40:54 +02:00
Alireza Ahmadi
c257b83a72 Merge pull request #1465 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.9
Bump github.com/shirou/gopsutil/v4 from 4.24.8 to 4.24.9
2024-10-07 23:39:37 +02:00
Alireza Ahmadi
43be4f3dbe Merge pull request #1464 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.67.1
Bump google.golang.org/grpc from 1.67.0 to 1.67.1
2024-10-07 23:39:27 +02:00
dependabot[bot]
b0e85f3ab2 Bump github.com/shirou/gopsutil/v4 from 4.24.8 to 4.24.9
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.8 to 4.24.9.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.8...v4.24.9)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 21:18:22 +00:00
dependabot[bot]
7b356fcbde Bump google.golang.org/grpc from 1.67.0 to 1.67.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.0 to 1.67.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.0...v1.67.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 21:18:15 +00:00
Alireza Ahmadi
84af1d616e Merge pull request #1450 from caikun233/main
Update translate.zh_Hans.toml
2024-09-21 23:10:04 +02:00
Alireza Ahmadi
aace0d3280 Merge pull request #1438 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.8
Bump github.com/shirou/gopsutil/v4 from 4.24.7 to 4.24.8
2024-09-21 23:08:59 +02:00
Alireza Ahmadi
04f710b2f9 Merge pull request #1439 from alireza0/dependabot/go_modules/golang.org/x/text-0.18.0
Bump golang.org/x/text from 0.17.0 to 0.18.0
2024-09-21 23:08:47 +02:00
Alireza Ahmadi
460cb5d4fb Merge branch 'main' into dependabot/go_modules/golang.org/x/text-0.18.0 2024-09-21 23:08:40 +02:00
Alireza Ahmadi
fe679ecef1 Merge pull request #1444 from alireza0/dependabot/go_modules/gorm.io/gorm-1.25.12
Bump gorm.io/gorm from 1.25.11 to 1.25.12
2024-09-21 23:07:58 +02:00
Alireza Ahmadi
580169a1a1 Merge pull request #1457 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.67.0
Bump google.golang.org/grpc from 1.66.0 to 1.67.0
2024-09-21 23:07:42 +02:00
dependabot[bot]
0cd6e488bf Bump google.golang.org/grpc from 1.66.0 to 1.67.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.66.0 to 1.67.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.66.0...v1.67.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 21:29:08 +00:00
caikun233
b86bc3891d Update translate.zh_Hans.toml
The original translation will result in a prompt of "登录成功成功" after successful login, and it is different from the translation in other languages
2024-09-15 17:35:06 +08:00
dependabot[bot]
bf2e10e757 Bump gorm.io/gorm from 1.25.11 to 1.25.12
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.11 to 1.25.12.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.11...v1.25.12)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 21:11:10 +00:00
dependabot[bot]
2f8d53fc8e Bump golang.org/x/text from 0.17.0 to 0.18.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.17.0 to 0.18.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-04 21:36:31 +00:00
dependabot[bot]
477d5f68b4 Bump github.com/shirou/gopsutil/v4 from 4.24.7 to 4.24.8
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.7 to 4.24.8.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.7...v4.24.8)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 21:12:22 +00:00
Alireza Ahmadi
aaabbdacaa v1.8.6 2024-09-01 20:33:29 +02:00
Alireza Ahmadi
509fff0592 go 1.23 + update packages 2024-09-01 11:37:55 +02:00
Alireza Ahmadi
a84c055fd8 update splithttp
Co-Authored-By: mhsanaei <ho3ein.sanaei@gmail.com>
2024-09-01 11:37:22 +02:00
Alireza Ahmadi
91d107c5ae freedom redirect 2024-09-01 11:36:58 +02:00
Alireza Ahmadi
a9f295e4c6 freedom noise
Co-Authored-By: mhsanaei <ho3ein.sanaei@gmail.com>
2024-09-01 11:34:52 +02:00
Alireza Ahmadi
c6fb39ab29 Merge pull request #1419 from alireza0/dependabot/go_modules/github.com/pelletier/go-toml/v2-2.2.3
Bump github.com/pelletier/go-toml/v2 from 2.2.2 to 2.2.3
2024-08-31 20:40:00 +02:00
Alireza Ahmadi
b1a57ac237 Merge pull request #1398 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.7
Bump github.com/shirou/gopsutil/v4 from 4.24.6 to 4.24.7
2024-08-31 20:39:47 +02:00
Alireza Ahmadi
8584d933b3 [fragment] manual config for packets 2024-08-31 20:39:36 +02:00
dependabot[bot]
f79f552eb8 Bump github.com/shirou/gopsutil/v4 from 4.24.6 to 4.24.7
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.6 to 4.24.7.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.6...v4.24.7)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-31 18:39:28 +00:00
dependabot[bot]
57cec6fef3 Bump github.com/pelletier/go-toml/v2 from 2.2.2 to 2.2.3
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.2.2...v2.2.3)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-31 18:39:24 +00:00
Alireza Ahmadi
ffd2d44d7f Merge pull request #1427 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.24
Bump github.com/xtls/xray-core from 1.8.19 to 1.8.24
2024-08-31 20:38:04 +02:00
Alireza Ahmadi
1f918542ee fix restart after enabling user #1406 2024-08-31 19:57:11 +02:00
dependabot[bot]
c521d10472 Bump github.com/xtls/xray-core from 1.8.19 to 1.8.24
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.19 to 1.8.24.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.19...v1.8.24)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 21:26:55 +00:00
Alireza Ahmadi
055db3e9fb Merge pull request #1375 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.19
Bump github.com/xtls/xray-core from 1.8.17 to 1.8.19
2024-07-18 22:03:48 +02:00
Alireza Ahmadi
f79a33d712 httpsplit fix title 2024-07-18 21:36:01 +02:00
dependabot[bot]
7594ef4fe0 Bump github.com/xtls/xray-core from 1.8.17 to 1.8.19
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.17 to 1.8.19.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.17...v1.8.19)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 21:42:14 +00:00
Alireza Ahmadi
f50bc16b61 v1.8.5 2024-07-14 21:01:35 +02:00
Alireza Ahmadi
0bd090d087 [sub] add hour for remained time
Co-Authored-By: mhsanaei <ho3ein.sanaei@gmail.com>
2024-07-14 18:38:18 +02:00
Alireza Ahmadi
54b48123c5 grpc dial new method
Co-Authored-By: mhsanaei <ho3ein.sanaei@gmail.com>
2024-07-14 18:29:32 +02:00
Alireza Ahmadi
6affec09d3 [warp] enhanced + delete option #1055 2024-07-14 18:19:38 +02:00
Alireza Ahmadi
22ac7fe3cc fallback outbound in balancer #1244 2024-07-14 12:42:47 +02:00
Hassan Ali Gilani
f5ce84ae68 Add getClientTrafficsById (#1347)
* Add getClientTrafficsById
Ability for API to get traffics by uuid

* bugfix GetClientTrafficByID

* bugfix
GetClientTrafficByID

* Update
GetClientTrafficByID function by Alireza0 Recomendation

* update
Replace Logger Warning with Debug for GetClientTrafficByID

* Fix: return empty array instead of error in case of gorm.ErrRecordNotFound

---------

Co-authored-by: Ali Gilani <gilani1364@gmail.com>
2024-07-14 12:08:30 +02:00
Alireza Ahmadi
f6535dd83e disable mux for vision flow #1193 2024-07-14 04:11:08 +02:00
Alireza Ahmadi
4d9c68dd7b fix domain validator #1329 2024-07-14 03:36:05 +02:00
Alireza Ahmadi
84ecdae7ca safe login #1365 2024-07-14 03:19:12 +02:00
Alireza Ahmadi
073bfb8991 fix observatory data #1333 2024-07-14 02:43:17 +02:00
Alireza Ahmadi
3e5660e1af fix randomShortID #1338 2024-07-14 00:55:25 +02:00
Alireza Ahmadi
970eba043f Merge pull request #1359 from LOVECHEN/main
Xray v1.8.17 + update dependencies
2024-07-13 23:39:13 +02:00
LOVE
890191d106 Xray v1.8.17 2024-07-13 02:29:11 +08:00
Alireza Ahmadi
a7fb001902 support more os
Co-Authored-By: mhsanaei <ho3ein.sanaei@gmail.com>
2024-07-10 22:48:59 +02:00
Alireza Ahmadi
a443d3187e Merge pull request #1356 from MHSanaei/main
some changes
2024-07-10 22:02:17 +02:00
Alireza Ahmadi
ac2429bbbf Merge pull request #1355 from sigseg5/main
Fix "merging" of "Default" prompt with default value in `confirm()` function
2024-07-10 22:02:06 +02:00
mhsanaei
19a7a8574c typo fixed 2024-07-10 21:06:58 +02:00
mhsanaei
038faf0197 Expand arch support in downloadXray 2024-07-10 20:56:14 +02:00
mhsanaei
2211838592 fix wrong splithttp default
Co-Authored-By: mmmray <142015632+mmmray@users.noreply.github.com>
2024-07-10 20:56:14 +02:00
sigseg5
3deaff08cd Fix merging of Default prompt with default value in confirm() function 2024-07-10 19:12:35 +03:00
Alireza Ahmadi
689b462a5f Merge pull request #1341 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.24.6
Bump github.com/shirou/gopsutil/v4 from 4.24.5 to 4.24.6
2024-07-08 00:18:55 +02:00
Alireza Ahmadi
abe5a06340 Merge pull request #1343 from alireza0/dependabot/go_modules/google.golang.org/grpc-1.65.0
Bump google.golang.org/grpc from 1.64.0 to 1.65.0
2024-07-08 00:18:42 +02:00
dependabot[bot]
966c4e718e Bump google.golang.org/grpc from 1.64.0 to 1.65.0
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 21:27:22 +00:00
dependabot[bot]
40b3a115e5 Bump github.com/shirou/gopsutil/v4 from 4.24.5 to 4.24.6
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.5 to 4.24.6.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.5...v4.24.6)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 21:10:07 +00:00
Alireza Ahmadi
8f286663ea Merge pull request #1325 from alireza0/dependabot/go_modules/github.com/xtls/xray-core-1.8.16
Bump github.com/xtls/xray-core from 1.8.15 to 1.8.16
2024-06-29 15:53:39 +02:00
Alireza Ahmadi
34020c5445 Merge pull request #1330 from LOVECHEN/main
Xray-core v1.8.16
2024-06-29 15:53:25 +02:00
LOVECHEN
66645b30b7 Xray-core v1.8.16 2024-06-26 18:47:07 +08:00
dependabot[bot]
ae6f9093a9 Bump github.com/xtls/xray-core from 1.8.15 to 1.8.16
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.15 to 1.8.16.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.15...v1.8.16)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 07:31:24 +00:00
114 changed files with 6564 additions and 3857 deletions

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: alireza0 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # removed
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@@ -1,4 +1,9 @@
name: Docker Image CI
permissions:
contents: read
packages: write
on:
push:
tags:
@@ -10,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
@@ -31,6 +36,8 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: Login to Docker Hub
uses: docker/login-action@v3
@@ -50,6 +57,6 @@ jobs:
with:
context: .
push: true
platforms: linux/amd64,linux/arm64/v8, linux/arm/v7, linux/386
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,58 +1,81 @@
name: Release X-UI
on:
push:
tags:
- "*"
workflow_dispatch:
release:
types: [published]
push:
branches:
- main
tags:
- "*.*.*"
paths:
- '.github/workflows/release.yml'
- '**.js'
- '**.css'
- '**.html'
- '**.sh'
- '**.go'
- 'go.mod'
- 'go.sum'
- 'x-ui.service'
jobs:
build:
permissions:
contents: write
strategy:
matrix:
platform:
- amd64
- arm64
- armv7
- armv6
- 386
runs-on: ubuntu-20.04
- armv5
- s390x
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.22'
- name: Install dependencies
run: |
sudo apt-get update
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
fi
go-version-file: go.mod
check-latest: true
- name: Build x-ui
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
fi
go build -o xui-release -v main.go
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
case "${{ matrix.platform }}" in
amd64) BOOTLIN_ARCH="x86-64" ;;
arm64) BOOTLIN_ARCH="aarch64" ;;
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
386) BOOTLIN_ARCH="x86-i686" ;;
s390x) BOOTLIN_ARCH="s390x-z13" ;;
esac
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
echo "Downloading: $TARBALL_URL"
cd /tmp
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
tar -xf "$(basename "$TARBALL_URL")"
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
cd -
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
file xui-release
ldd xui-release || echo "Static binary confirmed"
mkdir x-ui
cp xui-release x-ui/
@@ -63,40 +86,136 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.15/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip
wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "armv6" ]; then
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
unzip Xray-linux-arm32-v6.zip
rm -f Xray-linux-arm32-v6.zip
elif [ "${{ matrix.platform }}" == "386" ]; then
wget ${Xray_URL}Xray-linux-32.zip
wget -q ${Xray_URL}Xray-linux-32.zip
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
elif [ "${{ matrix.platform }}" == "armv5" ]; then
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
unzip Xray-linux-arm32-v5.zip
rm -f Xray-linux-arm32-v5.zip
elif [ "${{ matrix.platform }}" == "s390x" ]; then
wget -q ${Xray_URL}Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip
fi
rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }}
cd ../..
- name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: x-ui-linux-${{ matrix.platform }}
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
- name: Upload files to GH release
uses: svenstaro/upload-release-action@v2
if: |
(github.event_name == 'release' && github.event.action == 'published') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-${{ matrix.platform }}.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
overwrite: true
prerelease: true
# =================================
# Windows Build
# =================================
build-windows:
name: Build for Windows
permissions:
contents: write
strategy:
matrix:
platform:
- amd64
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- name: Build X-UI for Windows
shell: pwsh
run: |
$env:CGO_ENABLED="1"
$env:GOOS="windows"
$env:GOARCH="amd64"
go build -ldflags "-w -s" -o xui-release.exe -v main.go
mkdir x-ui
Copy-Item xui-release.exe x-ui\
mkdir x-ui\bin
cd x-ui\bin
# Download Xray for Windows
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
Remove-Item "Xray-windows-64.zip"
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
Rename-Item xray.exe xray-windows-amd64.exe
cd ..
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
cd ..
- name: Package to Zip
shell: pwsh
run: |
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: x-ui-windows-amd64
path: ./x-ui-windows-amd64.zip
- name: Upload files to GH release
uses: svenstaro/upload-release-action@v2
if: |
(github.event_name == 'release' && github.event.action == 'published') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-windows-amd64.zip
asset_name: x-ui-windows-amd64.zip
overwrite: true
prerelease: true

3
.gitignore vendored
View File

@@ -14,4 +14,5 @@ dist/
release/
/release.sh
/x-ui
.DS_Store
.DS_Store
/dev/

94
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,94 @@
# Contribution guide
This document describes **development-only** setup options for contributing to the x-ui project.
Production deployments are **out of scope** for this guide.
For safety, Docker-based development is **strongly recommended**.
Local installation should be performed **only inside a VM or disposable environment**.
---
## Recommended: Docker development setup
### Prerequisites
- Docker
- Docker Compose (v2+)
- Git
### Steps
```bash
git clone https://github.com/alireza0/x-ui.git
# or:
# git clone https://github.com/<your_fork_name>/x-ui.git
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```
### Explore panel
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
- Default credentials: `admin` / `admin`
To stop and remove containers or `ctrl` + `C`:
```bash
docker compose down
```
### Environment
When using the Docker development setup, the [docker-compose.dev.yml](/docker-compose.dev.yml) file mounts the local database directory into the container:
```yaml
volumes:
- ./dev/db:/etc/x-ui
```
This means:
- [./dev/db](/dev/db/) on the host is used to persist panel state during development
- `/etc/x-ui` inside the container is the active database/config directory
- Data will survive container restarts but is isolated from the host system
- You can safely delete `./dev/db` to reset the development database
---
## Local development setup (unsafe on host OS)
This method runs scripts with root privileges and alters the host system.
Use **only inside a VM, container, or disposable test environment**.
### Prerequisites
- Ubuntu/Debian or Fedora (actually anything with systemd, just use proper package manager)
### Install dependencies
```bash
# Ubuntu / Debian
sudo apt update
sudo apt upgrade -y
sudo apt install -y golang unzip git wget
# Fedora
sudo dnf update -y
sudo dnf install -y golang unzip git wget
```
### Setup panel
```bash
git clone https://github.com/alireza0/x-ui.git
# or:
# git clone https://github.com/<your_fork_name>/x-ui.git
cd x-ui/
sudo bash x-ui.sh # Select -> 10. Start
```
### Explore panel
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
- Default credentials: `admin` / `admin`

View File

@@ -14,7 +14,7 @@ case $1 in
;;
armv7 | arm | arm32)
ARCH="arm32-v7a"
FNAME="arm32"
FNAME="arm"
;;
*)
ARCH="64"
@@ -23,12 +23,12 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.15/Xray-linux-${ARCH}.zip"
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
mv xray "xray-linux-${FNAME}"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -q "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
wget -q "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
cd ../../

View File

@@ -1,11 +1,11 @@
FROM golang:1.22-alpine AS builder
FROM golang:1.25-alpine AS builder
WORKDIR /app
ARG TARGETARCH
RUN apk --no-cache --update add build-base gcc wget unzip
COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go
RUN go build -ldflags "-w -s" -o build/x-ui main.go
RUN ./DockerInitFiles.sh "$TARGETARCH"
FROM alpine

View File

@@ -13,10 +13,9 @@
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/alireza7)
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
- Tezos (XTZ):
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
<a href="https://nowpayments.io/donation/alireza7" target="_blank" rel="noreferrer noopener">
<img src="https://nowpayments.io/images/embeds/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Quick Overview
| Features | Enable? |
@@ -40,12 +39,12 @@
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
```
## Install Custom Version
## Install Legacy Version
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `1.8.0`:
**Step 1:** To install an old version, use following installation command. e.g., version `1.8.0`:
```sh
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 1.8.0
VERSION=1.8.0 && bash <(curl -Ls "https://raw.githubusercontent.com/alireza0/x-ui/$VERSION/install.sh") $VERSION
```
## Manual Install & Upgrade
@@ -186,13 +185,6 @@ docker build -t x-ui .
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
- Dark/Light theme
## Recommended OS
- CentOS 8+
- Ubuntu 20+
- Debian 10+
- Fedora 36+
## Preview
![inbounds](./media/inbounds.png)
@@ -216,7 +208,6 @@ docker build -t x-ui .
| :----: | --------------------------------- | ----------------------------------------- |
| `GET` | `"/"` | Get all inbounds |
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
| `POST` | `"/add"` | Add inbound |
| `POST` | `"/del/:id"` | Delete inbound |
| `POST` | `"/update/:id"` | Update inbound |
@@ -224,17 +215,40 @@ docker build -t x-ui .
| `POST` | `"/:id/delClient/:clientId"` | Delete client by clientId\* |
| `POST` | `"/updateClient/:clientId"` | Update client by clientId\* |
| `GET` | `"/getClientTraffics/:email"` | Get client's traffic |
| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID |
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset client's traffic |
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
\*- The field `clientId` should be filled by:
- `client.id` for VMess and VLESS
- `client.password` for Trojan
- `client.email` for Shadowsocks
- `client.id` for VMess and VLESS
- `client.password` for Trojan
- `client.email` for Shadowsocks
- `/xui/API/server` base for following actions:
| Method | Path | Action |
| :----: | --------------------------------- | ----------------------------------------- |
| `GET` | `"/status"` | Get server status |
| `GET` | `"/getDb"` | Get database backup |
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
| `GET` | `"/getConfigJson"` | Get config.json |
| `GET` | `"/getXrayVersion"` | Get last xray versions |
| `GET` | `"/getNewVlessEnc"` | Get new vless enc |
| `GET` | `"/getNewX25519Cert"` | Get new x25519 cert |
| `GET` | `"/getNewmldsa65"` | Get new mldsa65 |
| `POST` | `"/getNewEchCert"` | Get new ech cert |
| `POST` | `"/importDB"` | Import database to x-ui |
| `POST` | `"/stopXrayService"` | Stop xray service |
| `POST` | `"/restartXrayService"` | Restart xray service |
| `POST` | `"/installXray/:version"` | Install specific version of xray |
| `POST` | `"/logs/:count"` | Get panel/xray logs |
</details>

View File

@@ -4,6 +4,8 @@ import (
_ "embed"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
@@ -16,10 +18,10 @@ var name string
type LogLevel string
const (
Debug LogLevel = "debug"
Info LogLevel = "info"
Warn LogLevel = "warn"
Error LogLevel = "error"
Debug LogLevel = "debug"
Info LogLevel = "info"
Warning LogLevel = "warning"
Error LogLevel = "error"
)
func GetVersion() string {
@@ -53,12 +55,32 @@ func GetBinFolderPath() string {
return binFolderPath
}
func getBaseDir() string {
exePath, err := os.Executable()
if err != nil {
return "."
}
exeDir := filepath.Dir(exePath)
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
wd, err := os.Getwd()
if err != nil {
return "."
}
return wd
}
return exeDir
}
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" {
dbFolderPath = "/etc/x-ui"
if dbFolderPath != "" {
return dbFolderPath
}
return dbFolderPath
if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/etc/x-ui"
}
func GetDBPath() string {

View File

@@ -1 +1 @@
1.8.4
1.10.2

View File

@@ -7,9 +7,10 @@ import (
"os"
"path"
"x-ui/config"
"x-ui/database/model"
"x-ui/xray"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/xray"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
@@ -94,6 +95,17 @@ func InitDB(dbPath string) error {
return nil
}
func CloseDB() error {
if db != nil {
sqlDB, err := db.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
return nil
}
func GetDB() *gorm.DB {
return db
}
@@ -120,3 +132,26 @@ func Checkpoint() error {
}
return nil
}
func ValidateSQLiteDB(dbPath string) error {
if _, err := os.Stat(dbPath); err != nil { // file must exist
return err
}
gdb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Discard})
if err != nil {
return err
}
sqlDB, err := gdb.DB()
if err != nil {
return err
}
defer sqlDB.Close()
var res string
if err := gdb.Raw("PRAGMA integrity_check;").Scan(&res).Error; err != nil {
return err
}
if res != "ok" {
return common.NewError("sqlite integrity check failed: " + res)
}
return nil
}

View File

@@ -3,8 +3,8 @@ package model
import (
"fmt"
"x-ui/util/json_util"
"x-ui/xray"
"github.com/alireza0/x-ui/util/json_util"
"github.com/alireza0/x-ui/xray"
)
type Protocol string
@@ -79,3 +79,10 @@ type Client struct {
SubID string `json:"subId" form:"subId"`
Reset int `json:"reset" form:"reset"`
}
type VLESSSettings struct {
Clients []Client `json:"clients"`
Decryption string `json:"decryption"`
Encryption string `json:"encryption"`
Fallbacks []any `json:"fallbacks"`
}

15
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,15 @@
services:
xui:
build:
context: .
target: builder
working_dir: /app
volumes:
- .:/app
- ./dev/db:/etc/x-ui
environment:
XUI_BIN_FOLDER: /app/dev/bin
XUI_DB_FOLDER: /etc/x-ui
XUI_LOG_LEVEL: debug
XUI_DEBUG: "true"
command: ["go","run","./main.go"]

View File

@@ -1,5 +1,4 @@
---
version: "3"
services:
xui:

130
go.mod
View File

@@ -1,96 +1,90 @@
module x-ui
module github.com/alireza0/x-ui
go 1.22.0
go 1.25.7
require (
github.com/Calidity/gin-sessions v1.3.1
github.com/gin-gonic/gin v1.10.0
github.com/gin-contrib/gzip v1.2.5
github.com/gin-contrib/sessions v1.0.4
github.com/gin-gonic/gin v1.11.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/goccy/go-json v0.10.3
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/goccy/go-json v0.10.5
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.2
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
github.com/xtls/xray-core v1.8.15
github.com/shirou/gopsutil/v4 v4.26.1
github.com/xtls/xray-core v1.260206.0
go.uber.org/atomic v1.11.0
golang.org/x/text v0.16.0
google.golang.org/grpc v1.64.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10
golang.org/x/text v0.34.0
google.golang.org/grpc v1.79.1
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1
)
require (
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mattn/go-sqlite3 v1.14.34 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a // indirect
github.com/quic-go/quic-go v0.45.0 // indirect
github.com/refraction-networking/utls v1.6.6 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sagernet/sing v0.4.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shirou/gopsutil/v4 v4.24.5
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/pires/go-proxyproto v0.9.2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.7.17 // indirect
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
)

478
go.sum
View File

@@ -1,63 +1,42 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -67,325 +46,198 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a h1:XCUtNgBnZfUBhdfCX2QK+fslr9vevSsUg3W3peZwlak=
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM=
github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.7.17 h1:Jg4RUYIaQWTi7iY5ROHi3/Zsgxn4SPoRTwbdt35mt50=
github.com/sagernet/sing v0.7.17/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.15 h1:8lmDV0TaqSz0Agdh4dqQstg2QJa183j2D59PKOTVc+Y=
github.com/xtls/xray-core v1.8.15/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/xtls/xray-core v1.260206.0 h1:gY8IV6u76CW93txL9QmacgZ0Udxr2Q3e9qUxXAhdHqI=
github.com/xtls/xray-core v1.260206.0/go.mod h1:GyFIgVGRJkt3eyV/NMcdxOKXcJPqGGpyupHzy16uJhU=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=

View File

@@ -29,82 +29,97 @@ arch() {
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;;
armv6* | armv6) echo 'armv6' ;;
armv5* | armv5) echo 'armv5' ;;
s390x) echo 's390x' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac
}
echo "arch: $(arch)"
os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
if [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
fi
elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
fi
elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
fi
else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
fi
install_dependencies() {
case "${release}" in
centos)
yum -y update && yum install -y -q wget curl tar tzdata
ubuntu | debian | armbian)
apt-get update && apt-get install -y -q wget curl tar tzdata cron
;;
fedora)
dnf -y update && dnf install -y -q wget curl tar tzdata
centos | almalinux | rocky | ol)
yum -y update && yum install -y -q wget curl tar tzdata cronie
;;
fedora | amzn)
dnf -y update && dnf install -y -q wget curl tar tzdata cronie
;;
arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata cronie
;;
opensuse-tumbleweed)
zypper refresh && zypper -q install -y wget curl tar timezone cron
;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
apt-get update && apt install -y -q wget curl tar tzdata cron
;;
esac
}
#This function will be called when user installed x-ui out of sercurity
gen_random_string() {
local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1)
echo "$random_string"
}
config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username:" config_account
echo -e "${yellow}Your username will be:${config_account}${plain}"
read -p "Please set up your password:" config_password
echo -e "${yellow}Your password will be:${config_password}${plain}"
read -p "Please set up the panel port:" config_port
echo -e "${yellow}Your panel port is:${config_port}${plain}"
echo -e "${yellow}Initializing, please wait...${plain}"
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
echo -e "${yellow}Account name and password set successfully!${plain}"
/usr/local/x-ui/x-ui setting -port ${config_port}
echo -e "${yellow}Panel port set successfully!${plain}"
else
echo -e "${red}cancel...${plain}"
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
local usernameTemp=$(head -c 6 /dev/urandom | base64)
local passwordTemp=$(head -c 6 /dev/urandom | base64)
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp}
echo -e "this is a fresh installation,will generate random login info for security concerns:"
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
if [[ ${#existing_webBasePath} -lt 4 ]]; then
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
local config_webBasePath=$(gen_random_string 15)
local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10)
read -p "Would you like to customize the Panel Port settings? (If not, random port will be applied) [y/n]: " config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up the panel port: " config_port
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
else
local config_port=$(shuf -i 1024-62000 -n 1)
echo -e "${yellow}Generated random port: ${config_port}${plain}"
fi
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
echo -e "This is a fresh installation, generating random login info for security concerns:"
echo -e "###############################################"
echo -e "${green}username:${usernameTemp}${plain}"
echo -e "${green}password:${passwordTemp}${plain}"
echo -e "${green}Username: ${config_username}${plain}"
echo -e "${green}Password: ${config_password}${plain}"
echo -e "${green}Port: ${config_port}${plain}"
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
echo -e "###############################################"
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}"
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
else
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
local config_webBasePath=$(gen_random_string 15)
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
fi
else
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10)
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
echo -e "Generated new random login credentials:"
echo -e "###############################################"
echo -e "${green}Username: ${config_username}${plain}"
echo -e "${green}Password: ${config_password}${plain}"
echo -e "###############################################"
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
else
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
fi
fi
/usr/local/x-ui/x-ui migrate
}
@@ -141,10 +156,10 @@ install_x-ui() {
else
last_version=$1
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui v$1"
echo -e "Beginning to install x-ui $1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}"
echo -e "${red}download x-ui $1 failed,please check the version exists${plain}"
exit 1
fi
fi
@@ -181,8 +196,11 @@ install_x-ui() {
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
echo -e "${green}x-ui ${last_version}${plain} installation finished, it is up and running now..."
echo -e ""
echo -e "You may access the Panel with following URL(s):${yellow}"
/usr/local/x-ui/x-ui uri
echo -e "${plain}"
echo "X-UI Control Menu Usage"
echo "------------------------------------------"
echo "SUBCOMMANDS:"
@@ -191,6 +209,7 @@ install_x-ui() {
echo "x-ui stop - Stop"
echo "x-ui restart - Restart"
echo "x-ui status - Current Status"
echo "x-ui settings - Current Settings"
echo "x-ui enable - Enable Autostart on OS Startup"
echo "x-ui disable - Disable Autostart on OS Startup"
echo "x-ui log - Check Logs"

233
main.go
View File

@@ -3,21 +3,26 @@ package main
import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
_ "unsafe"
"x-ui/config"
"x-ui/database"
"x-ui/logger"
"x-ui/sub"
"x-ui/web"
"x-ui/web/global"
"x-ui/web/service"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/sub"
"github.com/alireza0/x-ui/util/sys"
"github.com/alireza0/x-ui/web"
"github.com/alireza0/x-ui/web/global"
"github.com/alireza0/x-ui/web/service"
"github.com/op/go-logging"
"github.com/shirou/gopsutil/v4/net"
)
func runWebServer() {
@@ -28,7 +33,7 @@ func runWebServer() {
logger.InitLogger(logging.DEBUG)
case config.Info:
logger.InitLogger(logging.INFO)
case config.Warn:
case config.Warning:
logger.InitLogger(logging.WARNING)
case config.Error:
logger.InitLogger(logging.ERROR)
@@ -63,7 +68,7 @@ func runWebServer() {
sigCh := make(chan os.Signal, 1)
// Trap shutdown signals
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, sys.SIGUSR1)
for {
sig := <-sigCh
@@ -94,6 +99,12 @@ func runWebServer() {
log.Println(err)
return
}
case sys.SIGUSR1:
logger.Info("Received USR1 signal, restarting xray-core...")
err := server.RestartXray()
if err != nil {
logger.Error("Failed to restart xray-core:", err)
}
default:
server.Stop()
subServer.Stop()
@@ -123,22 +134,35 @@ func showSetting(show bool) {
settingService := service.SettingService{}
port, err := settingService.GetPort()
if err != nil {
fmt.Println("get current port failed,error info:", err)
fmt.Println("get current port failed, error info:", err)
}
webBasePath, err := settingService.GetBasePath()
if err != nil {
fmt.Println("get webBasePath failed, error info:", err)
}
userService := service.UserService{}
userModel, err := userService.GetFirstUser()
if err != nil {
fmt.Println("get current user info failed,error info:", err)
fmt.Println("get current user info failed, error info:", err)
}
username := userModel.Username
userpasswd := userModel.Password
if (username == "") || (userpasswd == "") {
if username == "" || userpasswd == "" {
fmt.Println("current username or password is empty")
}
fmt.Println("current panel settings as follows:")
fmt.Println("username:", username)
fmt.Println("userpasswd:", userpasswd)
fmt.Println("password:", userpasswd)
fmt.Println("port:", port)
if webBasePath != "" {
fmt.Println("webBasePath:", webBasePath)
} else {
fmt.Println("webBasePath is not set")
}
}
}
@@ -201,7 +225,72 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
}
}
func updateSetting(port int, username string, password string) {
func updateSetting(port int, username string, password string, webBasePath string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println("Database initialization failed:", err)
return
}
settingService := service.SettingService{}
userService := service.UserService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("Failed to set port:", err)
} else {
fmt.Printf("Port set successfully: %v\n", port)
}
}
if username != "" || password != "" {
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("Failed to update username and password:", err)
} else {
fmt.Println("Username and password updated successfully")
}
}
if webBasePath != "" {
err := settingService.SetBasePath(webBasePath)
if err != nil {
fmt.Println("Failed to set base URI path:", err)
} else {
fmt.Println("Base URI path set successfully")
}
}
}
func updateCert(publicKey string, privateKey string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
return
}
if (privateKey != "" && publicKey != "") || (privateKey == "" && publicKey == "") {
settingService := service.SettingService{}
err = settingService.SetCertFile(publicKey)
if err != nil {
fmt.Println("set certificate public key failed:", err)
} else {
fmt.Println("set certificate public key success")
}
err = settingService.SetKeyFile(privateKey)
if err != nil {
fmt.Println("set certificate private key failed:", err)
} else {
fmt.Println("set certificate private key success")
}
} else {
fmt.Println("both public and private key should be entered.")
}
}
func getPanelURI() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
@@ -210,21 +299,64 @@ func updateSetting(port int, username string, password string) {
settingService := service.SettingService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("set port failed:", err)
} else {
fmt.Printf("set port %v success", port)
Port, _ := settingService.GetPort()
BasePath, _ := settingService.GetBasePath()
Listen, _ := settingService.GetListen()
Domain, _ := settingService.GetWebDomain()
KeyFile, _ := settingService.GetKeyFile()
CertFile, _ := settingService.GetCertFile()
TLS := false
if KeyFile != "" && CertFile != "" {
TLS = true
}
Proto := ""
if TLS {
Proto = "https://"
} else {
Proto = "http://"
}
PortText := fmt.Sprintf(":%d", Port)
if (Port == 443 && TLS) || (Port == 80 && !TLS) {
PortText = ""
}
if len(Domain) > 0 {
fmt.Println(Proto + Domain + PortText + BasePath)
return
}
if len(Listen) > 0 {
fmt.Println(Proto + Listen + PortText + BasePath)
return
}
fmt.Println("Local address:")
// get ip address
netInterfaces, _ := net.Interfaces()
for i := 0; i < len(netInterfaces); i++ {
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
addrs := netInterfaces[i].Addrs
for _, address := range addrs {
IP := strings.Split(address.Addr, "/")[0]
if strings.Contains(address.Addr, ".") {
fmt.Println(Proto + IP + PortText + BasePath)
} else if address.Addr[0:6] != "fe80::" {
fmt.Println(Proto + "[" + IP + "]" + PortText + BasePath)
}
}
}
}
if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("set username and password failed:", err)
} else {
fmt.Println("set username and password success")
resp, err := http.Get("https://api.ipify.org?format=text")
if err == nil {
defer resp.Body.Close()
ip, err := io.ReadAll(resp.Body)
if err == nil {
fmt.Printf("\nGlobal address:\n%s%s%s%s\n", Proto, ip, PortText, BasePath)
}
}
}
@@ -256,30 +388,37 @@ func main() {
var port int
var username string
var password string
var webBasePath string
var webCertFile string
var webKeyFile string
var tgbottoken string
var tgbotchatid string
var enabletgbot bool
var tgbotRuntime string
var reset bool
var show bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings")
settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Show current settings")
settingCmd.IntVar(&port, "port", 0, "Set panel port")
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable telegram bot notify")
oldUsage := flag.Usage
flag.Usage = func() {
oldUsage()
fmt.Println()
fmt.Println("Commands:")
fmt.Println(" run run web panel")
fmt.Println(" migrate migrate form other/old x-ui")
fmt.Println(" setting set settings")
fmt.Println(" run Run web panel")
fmt.Println(" uri Show panel URI")
fmt.Println(" migrate Migrate form other/old x-ui")
fmt.Println(" setting Set settings")
}
flag.Parse()
@@ -296,6 +435,8 @@ func main() {
return
}
runWebServer()
case "uri":
getPanelURI()
case "migrate":
migrateDb()
case "setting":
@@ -307,7 +448,7 @@ func main() {
if reset {
resetSetting()
} else {
updateSetting(port, username, password)
updateSetting(port, username, password, webBasePath)
}
if show {
showSetting(show)
@@ -318,6 +459,18 @@ func main() {
if enabletgbot {
updateTgbotEnableSts(enabletgbot)
}
case "cert":
err := settingCmd.Parse(os.Args[2:])
if err != nil {
fmt.Println(err)
return
}
if reset {
updateCert("", "")
} else {
updateCert(webCertFile, webKeyFile)
}
default:
fmt.Println("Invalid subcommands")
fmt.Println()

View File

@@ -43,11 +43,13 @@
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
"domainStrategy": "UseIP",
"noises": [],
"redirect": ""
},
"tag": "direct"
},
{
"tag": "block",

View File

@@ -8,12 +8,12 @@ import (
"net/http"
"strconv"
"x-ui/config"
"x-ui/logger"
"x-ui/util/common"
"x-ui/web/middleware"
"x-ui/web/network"
"x-ui/web/service"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/web/middleware"
"github.com/alireza0/x-ui/web/network"
"github.com/alireza0/x-ui/web/service"
"github.com/gin-gonic/gin"
)
@@ -92,6 +92,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
SubJsonFragment = ""
}
SubJsonNoises, err := s.settingService.GetSubJsonNoises()
if err != nil {
SubJsonNoises = ""
}
SubJsonMux, err := s.settingService.GetSubJsonMux()
if err != nil {
SubJsonMux = ""
@@ -106,7 +111,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
s.sub = NewSUBController(
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
SubJsonFragment, SubJsonMux, SubJsonRules)
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules)
return engine, nil
}

View File

@@ -3,6 +3,7 @@ package sub
import (
"encoding/base64"
"net"
"strings"
"github.com/gin-gonic/gin"
)
@@ -26,6 +27,7 @@ func NewSUBController(
rModel string,
update string,
jsonFragment string,
jsonNoise string,
jsonMux string,
jsonRules string,
) *SUBController {
@@ -37,7 +39,7 @@ func NewSUBController(
updateInterval: update,
subService: sub,
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
}
a.initRouter(g)
return a
@@ -54,7 +56,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid")
host, _, _ := net.SplitHostPort(c.Request.Host)
host := c.Request.Host
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
host, _, _ = net.SplitHostPort(c.Request.Host)
}
subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 {
c.String(400, "Error!")
@@ -79,7 +84,10 @@ func (a *SUBController) subs(c *gin.Context) {
func (a *SUBController) subJsons(c *gin.Context) {
subId := c.Param("subid")
host, _, _ := net.SplitHostPort(c.Request.Host)
host := c.Request.Host
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
host, _, _ = net.SplitHostPort(c.Request.Host)
}
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")

View File

@@ -6,12 +6,12 @@ import (
"fmt"
"strings"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/json_util"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/json_util"
"github.com/alireza0/x-ui/util/random"
"github.com/alireza0/x-ui/web/service"
"github.com/alireza0/x-ui/xray"
)
//go:embed default.json
@@ -20,14 +20,14 @@ var defaultJson string
type SubJsonService struct {
configJson map[string]interface{}
defaultOutbounds []json_util.RawMessage
fragment string
fragmentOrNoises bool
mux string
inboundService service.InboundService
SubService *SubService
}
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
var configJson map[string]interface{}
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
@@ -38,6 +38,31 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
}
}
fragmentOrNoises := false
if fragment != "" || noises != "" {
fragmentOrNoises = true
defaultOutboundsSettings := map[string]interface{}{
"domainStrategy": "UseIP",
"redirect": "",
}
if fragment != "" {
defaultOutboundsSettings["fragment"] = json_util.RawMessage(fragment)
}
if noises != "" {
defaultOutboundsSettings["noises"] = json_util.RawMessage(noises)
}
defaultDirectOutbound := map[string]interface{}{
"protocol": "freedom",
"settings": defaultOutboundsSettings,
"tag": "direct_out",
}
jsonBytes, _ := json.MarshalIndent(defaultDirectOutbound, "", " ")
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
if rules != "" {
var newRules []interface{}
routing, _ := configJson["routing"].(map[string]interface{})
@@ -48,14 +73,10 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
configJson["routing"] = routing
}
if fragment != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
}
return &SubJsonService{
configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
fragmentOrNoises: fragmentOrNoises,
mux: mux,
SubService: subService,
}
@@ -165,12 +186,12 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
case "tls":
if newStream["security"] != "tls" {
newStream["security"] = "tls"
newStream["tslSettings"] = map[string]interface{}{}
newStream["tlsSettings"] = map[string]interface{}{}
}
case "none":
if newStream["security"] != "none" {
newStream["security"] = "none"
delete(newStream, "tslSettings")
delete(newStream, "tlsSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
@@ -178,8 +199,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
var newOutbounds []json_util.RawMessage
switch inbound.Protocol {
case "vmess", "vless":
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
case "vmess":
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, ""))
case "vless":
var vlessSettings model.VLESSSettings
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
newOutbounds = append(newOutbounds,
s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
}
@@ -210,8 +237,8 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
}
delete(streamSettings, "sockopt")
if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
if s.fragmentOrNoises {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "direct_out", "tcpKeepAliveIdle": 100}`)
}
// remove proxy protocol
@@ -257,6 +284,7 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)
@@ -276,32 +304,43 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
return rltyData
}
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage {
outbound := Outbound{}
usersData := make([]UserVnext, 1)
usersData[0].ID = client.ID
usersData[0].Level = 8
if inbound.Protocol == model.VLESS {
usersData[0].Flow = client.Flow
usersData[0].Encryption = "none"
}
vnextData := make([]VnextSetting, 1)
vnextData[0] = VnextSetting{
Address: inbound.Listen,
Port: inbound.Port,
Users: usersData,
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = "proxy"
if s.mux != "" {
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Vnext: vnextData,
// Build standard Xray/V2Ray schema
user := map[string]any{
"id": client.ID,
"level": 8,
}
if inbound.Protocol == model.VLESS {
user["encryption"] = encryption
if client.Flow != "" {
user["flow"] = client.Flow
}
} else {
// VMess
user["alterId"] = 0
user["security"] = "auto"
}
vnext := map[string]any{
"address": inbound.Listen,
"port": inbound.Port,
"users": []any{user},
}
outbound.Settings = map[string]any{
"vnext": []any{vnext},
}
result, _ := json.MarshalIndent(outbound, "", " ")
@@ -339,8 +378,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Servers: serverData,
outbound.Settings = map[string]any{
"servers": serverData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
@@ -348,30 +387,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
}
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux json_util.RawMessage `json:"mux,omitempty"`
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
Settings OutboundSettings `json:"settings,omitempty"`
}
type OutboundSettings struct {
Vnext []VnextSetting `json:"vnext,omitempty"`
Servers []ServerSetting `json:"servers,omitempty"`
}
type VnextSetting struct {
Address string `json:"address"`
Port int `json:"port"`
Users []UserVnext `json:"users"`
}
type UserVnext struct {
Encryption string `json:"encryption,omitempty"`
Flow string `json:"flow,omitempty"`
ID string `json:"id"`
Level int `json:"level"`
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux json_util.RawMessage `json:"mux,omitempty"`
Settings map[string]any `json:"settings,omitempty"`
}
type ServerSetting struct {

View File

@@ -7,13 +7,13 @@ import (
"strings"
"time"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/util/random"
"github.com/alireza0/x-ui/web/service"
"github.com/alireza0/x-ui/xray"
"github.com/goccy/go-json"
)
@@ -200,17 +200,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
headers, _ := ws["headers"].(map[string]interface{})
obj["host"] = searchHost(headers)
}
case "http":
obj["net"] = "h2"
http, _ := stream["httpSettings"].(map[string]interface{})
obj["path"], _ = http["path"].(string)
obj["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
header := quic["header"].(map[string]interface{})
obj["type"], _ = header["type"].(string)
obj["host"], _ = quic["security"].(string)
obj["path"], _ = quic["key"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
obj["path"], _ = grpc["serviceName"].(string)
@@ -227,15 +216,16 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
headers, _ := httpupgrade["headers"].(map[string]interface{})
obj["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
obj["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
obj["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
obj["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]interface{})
obj["host"] = searchHost(headers)
}
obj["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
@@ -315,6 +305,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.VLESS {
return ""
}
var vlessSettings model.VLESSSettings
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
var stream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
@@ -329,6 +322,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
port := inbound.Port
streamNetwork := stream["network"].(string)
params := make(map[string]string)
if vlessSettings.Encryption != "" {
params["encryption"] = vlessSettings.Encryption
}
params["type"] = streamNetwork
switch streamNetwork {
@@ -358,16 +354,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string)
@@ -384,15 +370,16 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
headers, _ := httpupgrade["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
@@ -448,6 +435,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["fp"] = fp
}
}
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
params["pqv"] = pqv
}
}
params["spx"] = "/" + random.Seq(15)
}
@@ -561,16 +553,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string)
@@ -587,15 +569,16 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
headers, _ := httpupgrade["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
@@ -647,6 +630,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["fp"] = fp
}
}
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
params["pqv"] = pqv
}
}
params["spx"] = "/" + random.Seq(15)
}
}
@@ -760,16 +748,6 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
headers, _ := ws["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string)
@@ -786,15 +764,16 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
headers, _ := httpupgrade["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]interface{})
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
@@ -933,9 +912,36 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
now := time.Now().Unix()
switch exp := stats.ExpiryTime / 1000; {
case exp > 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
remainingSeconds := exp - now
days := remainingSeconds / 86400
hours := (remainingSeconds % 86400) / 3600
minutes := (remainingSeconds % 3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
case exp < 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
days := exp / -86400
hours := (exp % -86400) / 3600
minutes := (exp % -3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
}
}
}

View File

@@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"x-ui/logger"
"github.com/alireza0/x-ui/logger"
)
func NewErrorf(format string, a ...interface{}) error {

View File

@@ -1,8 +1,8 @@
package random
import (
"math/rand"
"time"
"crypto/rand"
"math/big"
)
var (
@@ -15,8 +15,6 @@ var (
)
func init() {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ {
numSeq[i] = rune('0' + i)
}
@@ -39,11 +37,20 @@ func init() {
func Seq(n int) string {
runes := make([]rune, n)
for i := 0; i < n; i++ {
runes[i] = allSeq[rand.Intn(len(allSeq))]
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allSeq))))
if err != nil {
panic("crypto/rand failed: " + err.Error())
}
runes[i] = allSeq[idx.Int64()]
}
return string(runes)
}
func Num(n int) int {
return rand.Intn(n)
bn := big.NewInt(int64(n))
r, err := rand.Int(rand.Reader, bn)
if err != nil {
panic("crypto/rand failed: " + err.Error())
}
return int(r.Int64())
}

View File

@@ -4,9 +4,13 @@
package sys
import (
"syscall"
"github.com/shirou/gopsutil/v4/net"
)
var SIGUSR1 = syscall.SIGUSR1
func GetTCPCount() (int, error) {
stats, err := net.Connections("tcp")
if err != nil {

View File

@@ -8,8 +8,11 @@ import (
"fmt"
"io"
"os"
"syscall"
)
var SIGUSR1 = syscall.SIGUSR1
func getLinesNum(filename string) (int, error) {
file, err := os.Open(filename)
if err != nil {

View File

@@ -4,9 +4,13 @@
package sys
import (
"syscall"
"github.com/shirou/gopsutil/v4/net"
)
var SIGUSR1 = syscall.Signal(0)
func GetTCPCount() (int, error) {
stats, err := net.Connections("tcp")
if err != nil {

File diff suppressed because one or more lines are too long

View File

@@ -34,13 +34,13 @@ function getLang() {
lang = window.navigator.language || window.navigator.userLanguage;
if (isSupportLang(lang)) {
setCookie('lang', lang, 150);
setCookie('lang', lang);
} else {
setCookie('lang', 'en-US', 150);
setCookie('lang', 'en-US');
window.location.reload();
}
} else {
setCookie('lang', 'en-US', 150);
setCookie('lang', 'en-US');
window.location.reload();
}
}
@@ -53,7 +53,7 @@ function setLang(lang) {
lang = 'en-US';
}
setCookie('lang', lang, 150);
setCookie('lang', lang);
window.location.reload();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@ class AllSetting {
this.subURI = "";
this.subJsonURI = "";
this.subJsonFragment = "";
this.subJsonNoises = "";
this.subJsonMux = "";
this.subJsonRules = "";

View File

@@ -95,10 +95,13 @@ function getCookie(cname) {
}
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
let expires = "";
if (exdays > 0) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
expires = "expires=" + d.toUTCString();
}
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=" + axios.defaults.baseURL;
}
function usageColor(data, threshold, total) {

View File

@@ -137,8 +137,8 @@ class RandomUtil {
static randomShortId() {
let shortIds = new Array(24).fill('');
for (var ii = 0; ii < 24; ii++) {
for (var jj = 0; jj < this.randomInt(16); jj++){
for (var ii = 1; ii < 24; ii++) {
for (var jj = 0; jj <= this.randomInt(7); jj++){
let randomNum = this.randomInt(256);
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
package controller
import (
"x-ui/web/service"
"github.com/alireza0/x-ui/web/service"
"github.com/gin-gonic/gin"
)
@@ -9,30 +9,40 @@ import (
type APIController struct {
BaseController
inboundController *InboundController
serverController *ServerController
Tgbot service.Tgbot
}
func NewAPIController(g *gin.RouterGroup) *APIController {
a := &APIController{}
func NewAPIController(g *gin.RouterGroup, s *ServerController) *APIController {
a := &APIController{
serverController: s,
}
a.initRouter(g)
return a
}
func (a *APIController) initRouter(g *gin.RouterGroup) {
g = g.Group("/xui/API/inbounds")
g.Use(a.checkLogin)
api := g.Group("/xui/API")
api.Use(a.checkLogin)
a.inboundController = NewInboundController(g)
a.inboundApi(api)
a.serverApi(api)
}
func (a *APIController) inboundApi(api *gin.RouterGroup) {
inboundsApi := api.Group("/inbounds")
a.inboundController = &InboundController{}
inboundRoutes := []struct {
Method string
Path string
Handler gin.HandlerFunc
}{
{"GET", "/createbackup", a.createBackup},
{"GET", "/", a.inboundController.getInbounds},
{"GET", "/get/:id", a.inboundController.getInbound},
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
{"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById},
{"POST", "/add", a.inboundController.addInbound},
{"POST", "/del/:id", a.inboundController.delInbound},
{"POST", "/update/:id", a.inboundController.updateInbound},
@@ -47,7 +57,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
}
for _, route := range inboundRoutes {
g.Handle(route.Method, route.Path, route.Handler)
inboundsApi.Handle(route.Method, route.Path, route.Handler)
}
}
func (a *APIController) serverApi(api *gin.RouterGroup) {
serverApi := api.Group("/server")
serverRoutes := []struct {
Method string
Path string
Handler gin.HandlerFunc
}{
{"GET", "/status", a.serverController.status},
{"GET", "/getDb", a.serverController.getDb},
{"GET", "/createbackup", a.createBackup},
{"GET", "/getConfigJson", a.serverController.getConfigJson},
{"GET", "/getXrayVersion", a.serverController.getXrayVersion},
{"GET", "/getNewVlessEnc", a.serverController.getNewVlessEnc},
{"GET", "/getNewX25519Cert", a.serverController.getNewX25519Cert},
{"GET", "/getNewmldsa65", a.serverController.getNewmldsa65},
{"POST", "/getNewEchCert", a.serverController.getNewEchCert},
{"POST", "/importDB", a.serverController.importDB},
{"POST", "/stopXrayService", a.serverController.stopXrayService},
{"POST", "/restartXrayService", a.serverController.restartXrayService},
{"POST", "/installXray/:version", a.serverController.installXray},
{"POST", "/logs/:count", a.serverController.getLogs},
}
for _, route := range serverRoutes {
serverApi.Handle(route.Method, route.Path, route.Handler)
}
}

View File

@@ -3,9 +3,9 @@ package controller
import (
"net/http"
"x-ui/logger"
"x-ui/web/locale"
"x-ui/web/session"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/web/locale"
"github.com/alireza0/x-ui/web/session"
"github.com/gin-gonic/gin"
)

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"strconv"
"x-ui/database/model"
"x-ui/web/service"
"x-ui/web/session"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/web/service"
"github.com/alireza0/x-ui/web/session"
"github.com/gin-gonic/gin"
)
@@ -75,6 +75,15 @@ func (a *InboundController) getClientTraffics(c *gin.Context) {
jsonObj(c, clientTraffics, nil)
}
func (a *InboundController) getClientTrafficsById(c *gin.Context) {
id := c.Param("id")
clientTraffics, err := a.inboundService.GetClientTrafficByID(id)
if err != nil {
jsonMsg(c, "Error getting traffics", err)
return
}
jsonObj(c, clientTraffics, nil)
}
func (a *InboundController) addInbound(c *gin.Context) {
inbound := &model.Inbound{}
err := c.ShouldBind(inbound)
@@ -90,8 +99,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
@@ -104,8 +112,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "delete"), err)
return
}
needRestart := true
needRestart, err = a.inboundService.DelInbound(id)
needRestart, err := a.inboundService.DelInbound(id)
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
@@ -126,8 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
return
}
needRestart := true
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
inbound, needRestart, err := a.inboundService.UpdateInbound(inbound)
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
@@ -142,9 +148,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
return
}
needRestart := true
needRestart, err = a.inboundService.AddInboundClient(data)
needRestart, err := a.inboundService.AddInboundClient(data)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
@@ -163,9 +167,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
}
clientId := c.Param("clientId")
needRestart := true
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
needRestart, err := a.inboundService.DelInboundClient(id, clientId)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
@@ -186,9 +188,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
return
}
needRestart := true
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
needRestart, err := a.inboundService.UpdateInboundClient(inbound, clientId)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
@@ -207,14 +207,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
}
email := c.Param("email")
needRestart := true
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
if err != nil {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "traffic reseted", nil)
jsonMsg(c, "traffic has been reset", nil)
if needRestart {
a.xrayService.SetToNeedRestart()
}
@@ -228,7 +226,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics reseted", nil)
jsonMsg(c, "All traffics has been reset", nil)
}
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
@@ -245,7 +243,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
} else {
a.xrayService.SetToNeedRestart()
}
jsonMsg(c, "All traffics of client reseted", nil)
jsonMsg(c, "All traffics of client has been reset", nil)
}
func (a *InboundController) delDepletedClients(c *gin.Context) {
@@ -259,7 +257,7 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
jsonMsg(c, "Something went wrong!", err)
return
}
jsonMsg(c, "All delpeted clients are deleted", nil)
jsonMsg(c, "All depleted clients are deleted", nil)
}
func (a *InboundController) importInbound(c *gin.Context) {
@@ -283,8 +281,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
inbound.ClientStats[index].Enable = true
}
needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
if err == nil && needRestart {
a.xrayService.SetToNeedRestart()
@@ -292,5 +289,5 @@ func (a *InboundController) importInbound(c *gin.Context) {
}
func (a *InboundController) onlines(c *gin.Context) {
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
}

View File

@@ -2,11 +2,12 @@ package controller
import (
"net/http"
"text/template"
"time"
"x-ui/logger"
"x-ui/web/service"
"x-ui/web/session"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/web/service"
"github.com/alireza0/x-ui/web/session"
"github.com/gin-gonic/gin"
)
@@ -62,37 +63,31 @@ func (a *IndexController) login(c *gin.Context) {
user := a.userService.CheckUser(form.Username, form.Password)
timeStr := time.Now().Format("2006-01-02 15:04:05")
safeUser := template.HTMLEscapeString(form.Username)
safePass := template.HTMLEscapeString(form.Password)
if user == nil {
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
logger.Infof("wrong username or password: \"%s\" \"%s\"", safeUser, safePass)
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return
} else {
logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
}
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil {
logger.Infof("Unable to get session's max age from DB")
}
if sessionMaxAge > 0 {
err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil {
logger.Infof("Unable to set session's max age")
}
logger.Infof("%s Successful Login ,Ip Address: %s\n", safeUser, getRemoteIp(c))
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
}
err = session.SetLoginUser(c, user)
logger.Info("user", user.Id, "login success")
if err == nil {
logger.Infof("%s logged in successfully", user.Username)
} else {
logger.Error("Unable to set login user")
}
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
}
func (a *IndexController) logout(c *gin.Context) {
user := session.GetLoginUser(c)
if user != nil {
logger.Info("user", user.Id, "logout")
logger.Infof("%s logged out successfully", user.Username)
}
session.ClearSession(c)
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))

View File

@@ -6,8 +6,8 @@ import (
"regexp"
"time"
"x-ui/web/global"
"x-ui/web/service"
"github.com/alireza0/x-ui/web/global"
"github.com/alireza0/x-ui/web/service"
"github.com/gin-gonic/gin"
)
@@ -39,16 +39,20 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g = g.Group("/server")
g.Use(a.checkLogin)
g.POST("/status", a.status)
g.POST("/getXrayVersion", a.getXrayVersion)
g.GET("/status", a.status)
g.GET("/getDb", a.getDb)
g.GET("/getConfigJson", a.getConfigJson)
g.GET("/getNewmldsa65", a.getNewmldsa65)
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
g.GET("/getXrayVersion", a.getXrayVersion)
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
g.POST("/getNewEchCert", a.getNewEchCert)
g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray)
g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
}
func (a *ServerController) refreshStatus() {
@@ -105,7 +109,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
jsonMsg(c, "", err)
return
}
jsonMsg(c, "Xray stoped", err)
jsonMsg(c, "Xray stopped", err)
}
func (a *ServerController) restartXrayService(c *gin.Context) {
@@ -186,3 +190,31 @@ func (a *ServerController) getNewX25519Cert(c *gin.Context) {
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewmldsa65(c *gin.Context) {
cert, err := a.serverService.GetNewmldsa65()
if err != nil {
jsonMsg(c, "get mldsa65 certificate", err)
return
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewEchCert(c *gin.Context) {
sni := c.PostForm("sni")
cert, err := a.serverService.GetNewEchCert(sni)
if err != nil {
jsonMsg(c, "get ech certificate", err)
return
}
jsonObj(c, cert, nil)
}
func (a *ServerController) getNewVlessEnc(c *gin.Context) {
out, err := a.serverService.GetNewVlessEnc()
if err != nil {
jsonMsg(c, "Failed to generate vless encryption config", err)
return
}
jsonObj(c, out, nil)
}

View File

@@ -4,9 +4,9 @@ import (
"errors"
"time"
"x-ui/web/entity"
"x-ui/web/service"
"x-ui/web/session"
"github.com/alireza0/x-ui/web/entity"
"github.com/alireza0/x-ui/web/service"
"github.com/alireza0/x-ui/web/session"
"github.com/gin-gonic/gin"
)

View File

@@ -5,9 +5,9 @@ import (
"net/http"
"strings"
"x-ui/config"
"x-ui/logger"
"x-ui/web/entity"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/web/entity"
"github.com/gin-gonic/gin"
)
@@ -61,7 +61,11 @@ func html(c *gin.Context, name string, title string, data gin.H) {
data = gin.H{}
}
data["title"] = title
data["host"], _, _ = net.SplitHostPort(c.Request.Host)
host := c.Request.Host
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
host, _, _ = net.SplitHostPort(c.Request.Host)
}
data["host"] = host
data["request_uri"] = c.Request.RequestURI
data["base_path"] = c.GetString("base_path")
c.HTML(http.StatusOK, name, getContext(data))

View File

@@ -1,7 +1,7 @@
package controller
import (
"x-ui/web/service"
"github.com/alireza0/x-ui/web/service"
"github.com/gin-gonic/gin"
)
@@ -11,6 +11,7 @@ type XraySettingController struct {
SettingService service.SettingService
InboundService service.InboundService
XrayService service.XrayService
WarpService service.WarpService
}
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
@@ -69,16 +70,18 @@ func (a *XraySettingController) warp(c *gin.Context) {
var err error
switch action {
case "data":
resp, err = a.XraySettingService.GetWarp()
resp, err = a.WarpService.GetWarpData()
case "del":
err = a.WarpService.DelWarpData()
case "config":
resp, err = a.XraySettingService.GetWarpConfig()
resp, err = a.WarpService.GetWarpConfig()
case "reg":
skey := c.PostForm("privateKey")
pkey := c.PostForm("publicKey")
resp, err = a.XraySettingService.RegWarp(skey, pkey)
resp, err = a.WarpService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
resp, err = a.WarpService.SetWarpLicense(license)
}
jsonObj(c, resp, err)

View File

@@ -6,7 +6,7 @@ import (
"strings"
"time"
"x-ui/util/common"
"github.com/alireza0/x-ui/util/common"
)
type Msg struct {
@@ -50,6 +50,7 @@ type AllSetting struct {
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
}

View File

@@ -2,7 +2,7 @@
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="robots" content="noindex,nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">

View File

@@ -71,8 +71,8 @@
qrModal: qrModal,
},
methods: {
copyToClipboard(elmentId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
@@ -80,9 +80,9 @@
this.qrModal.clipboard.destroy();
});
},
setQrCode(elmentId, content) {
setQrCode(elementId, content) {
new QRious({
element: document.querySelector('#' + elmentId),
element: document.querySelector('#' + elementId),
size: 260,
value: content,
});

View File

@@ -13,10 +13,10 @@
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
@@ -25,7 +25,7 @@
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -67,7 +67,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
<a-input-number v-model.number="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>

View File

@@ -1,96 +1,114 @@
{{define "dnsModal"}}
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
<a-select
v-model="dnsModal.dnsServer.queryStrategy"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
[[ l ]]
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Skip Fallback' v-if="isAdvanced">
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
</a-form-item>
</a-form>
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true"
:mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')"></a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
<a-button icon="minus" size="small" slot="addonAfter"
@click="dnsModal.dnsServer.domains.splice(index,1)"></a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
<a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Skip Fallback' v-if="isAdvanced">
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.dns.expectIPs"}}'>
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.expectIPs.push('')"></a-button>
<template v-for="(domain, index) in dnsModal.dnsServer.expectIPs">
<a-input v-model.trim="dnsModal.dnsServer.expectIPs[index]">
<a-button icon="minus" size="small" slot="addonAfter"
@click="dnsModal.dnsServer.expectIPs.splice(index,1)"></a-button>
</a-input>
</template>
</a-form-item>
</a-form>
</a-modal>
<script>
const dnsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
dnsServer: {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
skipFallback: true,
},
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
dnsModal.dnsServer.domains = domains;
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
if (typeof dnsServer == 'object'){
this.dnsServer = dnsServer;
} else {
this.dnsServer = {
address: dnsServer?? "",
domains: [],
queryStrategy: 'UseIP',
skipFallback: true,
}
}
} else {
this.dnsServer = {
address: "localhost",
domains: [],
queryStrategy: 'UseIP',
skipFallback: true,
}
}
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
const dnsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
dnsServer: {
address: "localhost",
domains: [],
expectIPs: [],
queryStrategy: 'UseIP',
skipFallback: true,
},
ok() {
domains = dnsModal.dnsServer.domains.filter(d => d.length > 0);
expectIPs = dnsModal.dnsServer.expectIPs.filter(ip => ip.length > 0);
dnsModal.dnsServer.domains = domains;
dnsModal.dnsServer.expectIPs = expectIPs;
newDnsServer = (domains.length > 0 || expectIPs.length > 0) ? dnsModal.dnsServer : dnsModal.dnsServer.address;
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
},
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function () { return dnsModal.dnsServer.domains.length>0 }
show({
title = '',
okText = '{{ i18n "confirm" }}',
dnsServer,
confirm = (dnsServer) => { },
isEdit = false
}) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
if (typeof dnsServer == 'object') {
this.dnsServer = dnsServer;
} else {
this.dnsServer = {
address: dnsServer ?? "",
domains: [],
expectIPs: [],
queryStrategy: 'UseIP',
skipFallback: true,
}
}
});
} else {
this.dnsServer = {
address: "localhost",
domains: [],
expectIPs: [],
queryStrategy: 'UseIP',
skipFallback: true,
}
}
this.isEdit = isEdit;
},
close() {
dnsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#dns-modal',
data: {
dnsModal: dnsModal,
},
computed: {
isAdvanced: {
get: function () {
return dnsModal.dnsServer.domains.length > 0;
}
}
}
});
</script>
{{end}}
{{end}}

View File

@@ -7,7 +7,7 @@
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
<a-input-number v-model.number="fakednsModal.fakeDns.poolSize" :min="1"></a-input-number>
</a-form-item>
</a-form>
</a-modal>

View File

@@ -79,7 +79,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> GB
<a-input-number v-model.number="client._totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">

View File

@@ -28,7 +28,7 @@
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="inbound.port"></a-input-number>
<a-input-number v-model.number="inbound.port" :min="1" :max="65535"></a-input-number>
</a-form-item>
<a-form-item>
@@ -41,7 +41,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> GB
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number> GB
</a-form-item>
<a-form-item>
@@ -100,6 +100,11 @@
{{template "form/wireguard"}}
</template>
<!-- tun -->
<template v-if="inbound.protocol === Protocols.TUN">
{{template "form/tun"}}
</template>
<!-- stream settings -->
<template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}}
@@ -112,7 +117,9 @@
</template>
<!-- sniffing -->
<template>
{{template "form/sniffing"}}
</template>
<a-collapse>
<a-collapse-panel header='Sniffing'>
{{template "form/sniffing"}}
</a-collapse-panel>
</a-collapse>
{{end}}

View File

@@ -17,56 +17,92 @@
<!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom">
<a-form-item label='Strategy'>
<a-select
v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Fragment'>
<a-switch
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
<a-form-item label='Strategy'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Redirect'>
<a-input v-model="outbound.settings.redirect"></a-input>
</a-form-item>
<a-form-item label='Fragment'>
<a-switch :checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0">
<a-form-item label='Packets'>
<a-select
v-model="outbound.settings.fragment.packets"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Length'>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item>
<a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item>
<a-form-item label='Packets'>
<a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Length'>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item>
<a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item>
</template>
</template>
<!-- Switch for Noises -->
<a-form-item label='Noises'>
<a-switch :checked="outbound.settings.noises.length > 0"
@change="checked => outbound.settings.noises = checked ? [new Outbound.FreedomSettings.Noise()] : []">
</a-switch>
</a-form-item>
<!-- Add Noise Button -->
<template v-if="outbound.settings.noises.length > 0">
<a-form-item label="Noises">
<a-button icon="plus" type="primary" size="small" @click="outbound.settings.addNoise()"></a-button>
</a-form-item>
<!-- Noise Configurations -->
<a-form v-for="(noise, index) in outbound.settings.noises" :key="index" :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;"> Noise [[ index + 1 ]]
<a-icon v-if="outbound.settings.noises.length > 1" type="delete" @click="() => outbound.settings.delNoise(index)"
style="color: rgb(255, 77, 79); cursor: pointer;"></a-icon>
</a-divider>
<a-form-item label='Type'>
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['rand','base64','str','hex']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Packet'>
<a-input v-model.trim="noise.packet"></a-input>
</a-form-item>
<a-form-item label='Delay'>
<a-input v-model.trim="noise.delay"></a-input>
</a-form-item>
</a-form>
</template>
</template>
<!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'>
<a-select
v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Response Type'>
<a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select
v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='non-IP queries'>
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['drop','skip']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types'>
<a-input v-model.number="outbound.settings.blockTypes"></a-input>
</a-form-item>
</template>
<!-- wireguard settings -->
@@ -110,8 +146,8 @@
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
<a-form-item label='No Kernel Tun'>
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
@@ -169,12 +205,27 @@
</a-form-item>
</template>
<!-- Vnext (vless/vmess) settings -->
<!-- vless/vmess user settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input>
<a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item>
<!-- vmess settings -->
<template v-if="outbound.protocol === Protocols.VMess">
<a-form-item label='Security'>
<a-select v-model="outbound.settings.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- vless settings -->
<template v-if="outbound.protocol === Protocols.VLESS">
<a-form-item label='encryption'>
<a-input v-model.trim="outbound.settings.encryption"></a-input>
</a-form-item>
</template>
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -183,6 +234,28 @@
</a-select>
</a-form-item>
</template>
<!-- XTLS Vision Advanced Settings -->
<template v-if="outbound.canEnableVisionSeed()">
<a-form-item label="Vision Pre-Connect">
<a-input-number v-model.number="outbound.settings.testpre" :min="0" :max="10" :style="{ width: '100%' }" placeholder="0"></a-input-number>
</a-form-item>
<a-form-item label="Vision Seed">
<a-row :gutter="8">
<a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[0]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[1]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[2]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number v-model.number="outbound.settings.testseed[3]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
</a-col>
</a-row>
</a-form-item>
</template>
</template>
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
@@ -212,7 +285,14 @@
<a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</template>
</template>
<!-- hysteria settings -->
<template v-if="outbound.protocol === Protocols.Hysteria">
<a-form-item label='Version'>
<a-input-number v-model.number="outbound.settings.version" :min="2"
:max="2" disabled></a-input-number>
</a-form-item>
</template>
</template>
<!-- stream settings -->
@@ -220,14 +300,17 @@
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP/2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
<a-select-option value="splithttp">SplitHTTP</a-select-option>
<template v-if="outbound.protocol != Protocols.Hysteria">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
<a-select-option value="xhttp">XHTTP</a-select-option>
</template>
<template v-else>
<a-select-option value="hysteria">Hysteria2</a-select-option>
</template>
</a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'">
@@ -248,20 +331,6 @@
<!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model="outbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
@@ -294,41 +363,7 @@
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item>
</template>
<!-- http -->
<template v-if="outbound.stream.network === 'http'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.http.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.http.path"></a-input>
</a-form-item>
</template>
<!-- quic -->
<template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'">
<a-form-item label='Service Name'>
@@ -351,16 +386,169 @@
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
</a-form-item>
</template>
<!-- splithttp -->
<template v-if="outbound.stream.network === 'splithttp'">
<!-- xhttp -->
<template v-if="outbound.stream.network === 'xhttp'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.splithttp.host"></a-input>
<a-input v-model="outbound.stream.xhttp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.splithttp.path"></a-input>
<a-input v-model.trim="outbound.stream.xhttp.path"></a-input>
</a-form-item>
</template>
<a-form-item label='Mode'>
<a-select v-model="outbound.stream.xhttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="No gRPC Header"
v-if="outbound.stream.xhttp.mode === 'stream-up' || outbound.stream.xhttp.mode === 'stream-one'">
<a-switch v-model="outbound.stream.xhttp.noGRPCHeader"></a-switch>
</a-form-item>
<a-form-item label="Min Upload Interval (Ms)" v-if="outbound.stream.xhttp.mode === 'packet-up'">
<a-input v-model.trim="outbound.stream.xhttp.scMinPostsIntervalMs"></a-input>
</a-form-item>
<a-form-item label="Max Concurrency" v-if="!outbound.stream.xhttp.xmux.maxConnections">
<a-input v-model="outbound.stream.xhttp.xmux.maxConcurrency"></a-input>
</a-form-item>
<a-form-item label="Max Connections" v-if="!outbound.stream.xhttp.xmux.maxConcurrency">
<a-input v-model="outbound.stream.xhttp.xmux.maxConnections"></a-input>
</a-form-item>
<a-form-item label="Max Reuse Times">
<a-input v-model="outbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
</a-form-item>
<a-form-item label="Max Request Times">
<a-input v-model="outbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
</a-form-item>
<a-form-item label="Max Reusable Secs">
<a-input v-model="outbound.stream.xhttp.xmux.hMaxReusableSecs"></a-input>
</a-form-item>
<a-form-item label='Keep Alive Period'>
<a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
</a-form-item>
</template>
<!-- hysteria -->
<template v-if="outbound.stream.network === 'hysteria'">
<a-form-item label='Auth Password'>
<a-input v-model.trim="outbound.stream.hysteria.auth"></a-input>
</a-form-item>
<a-form-item label='Congestion'>
<a-select v-model="outbound.stream.hysteria.congestion"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>BBR (Auto)</a-select-option>
<a-select-option value="brutal">Brutal</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Upload Speed'>
<a-input v-model.trim="outbound.stream.hysteria.up"
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
</a-form-item>
<a-form-item label='Download Speed'>
<a-input v-model.trim="outbound.stream.hysteria.down"
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
</a-form-item>
<a-form-item label='UDP Hop Port'>
<a-input v-model.trim="outbound.stream.hysteria.udphopPort"
placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input>
</a-form-item>
<a-form-item label='Hop Interval Min (s)'
v-if="outbound.stream.hysteria.udphopPort">
<a-input-number
v-model.number="outbound.stream.hysteria.udphopIntervalMin"
:min="5"></a-input-number>
</a-form-item>
<a-form-item label='Hop Interval Max (s)'
v-if="outbound.stream.hysteria.udphopPort">
<a-input-number
v-model.number="outbound.stream.hysteria.udphopIntervalMax"
:min="5"></a-input-number>
</a-form-item>
<a-form-item label='Init Stream Receive'>
<a-input-number
v-model.number="outbound.stream.hysteria.initStreamReceiveWindow"></a-input-number>
</a-form-item>
<a-form-item label='Max Stream Receive'>
<a-input-number
v-model.number="outbound.stream.hysteria.maxStreamReceiveWindow"></a-input-number>
</a-form-item>
<a-form-item label='Init Connection Receive'>
<a-input-number
v-model.number="outbound.stream.hysteria.initConnectionReceiveWindow"></a-input-number>
</a-form-item>
<a-form-item label='Max Connection Receive'>
<a-input-number
v-model.number="outbound.stream.hysteria.maxConnectionReceiveWindow"></a-input-number>
</a-form-item>
<a-form-item label='Max Idle Timeout (s)'>
<a-input-number
v-model.number="outbound.stream.hysteria.maxIdleTimeout" :min="4"
:max="120"></a-input-number>
</a-form-item>
<a-form-item label='Keep Alive Period (s)'>
<a-input-number
v-model.number="outbound.stream.hysteria.keepAlivePeriod" :min="0"
:max="60"></a-input-number>
</a-form-item>
<a-form-item label='Disable Path MTU'>
<a-switch
v-model="outbound.stream.hysteria.disablePathMTUDiscovery"></a-switch>
</a-form-item>
</template>
</template>
<!-- finalmask settings -->
<template v-if="outbound.canEnableStream()">
<a-form-item label="UDP Masks">
<a-button icon="plus" type="primary" size="small"
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))"></a-button>
</a-form-item>
<template
v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0">
<a-form v-for="(mask, index) in outbound.stream.finalmask.udp"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
<a-icon type="delete"
@click="() => outbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Type'>
<a-select v-model="mask.type"
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option
v-if="outbound.protocol === Protocols.Hysteria"
value="salamander">
Salamander (Hysteria2)
</a-select-option>
<template v-if="outbound.stream.network === 'kcp'">
<a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
<a-select-option value="header-dns">Header DNS</a-select-option>
<a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
<a-select-option value="header-srtp">Header SRTP</a-select-option>
<a-select-option value="header-utp">Header uTP</a-select-option>
<a-select-option value="header-wechat">Header WeChat Video</a-select-option>
<a-select-option value="header-wireguard">Header WireGuard</a-select-option>
<a-select-option value="mkcp-original">mKCP Original</a-select-option>
</template>
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(outbound.stream.network)"
value="xdns">
xDNS (Experimental)
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Password'
v-if="['salamander', 'mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
</a-form-item>
<a-form-item label='Domain'
v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-input v-model.trim="mask.settings.domain"
placeholder="e.g., www.example.com"></a-input>
</a-form-item>
</a-form>
</template>
</template>
<!-- tls settings -->
@@ -392,9 +580,20 @@
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ECH Config List">
<a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="Verify Peer Cert By Name">
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByName"></a-input>
</a-form-item>
<a-form-item label="Pinned Peer Cert">
<a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256"
placeholder="Enter SHA256 fingerprints (base64)">
</a-input>
</a-form-item>
</template>
<!-- reality settings -->
@@ -417,6 +616,9 @@
<a-form-item label="Public Key">
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item>
<a-form-item label="mldsa65 Verify">
<a-input v-model.trim="outbound.stream.reality.mldsa65Verify"></a-input>
</a-form-item>
</template>
</template>
@@ -430,14 +632,31 @@
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Address Port Strategy'>
<a-select v-model="outbound.stream.sockopt.addressPortStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in Address_Port_Strategy" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Keep Alive Interval">
<a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Keep Alive Interval">
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
<a-form-item label="Multipath TCP">
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item>
<a-form-item label="TCP No-Delay">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
<a-form-item label="Penetrate">
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
</a-form-item>
<a-form-item label="Trusted X-Forwarded-For">
<a-select mode="tags" v-model="outbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
</a-select>
</a-form-item>
</template>
@@ -448,10 +667,10 @@
</a-form-item>
<template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency">
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
<a-input-number v-model.number="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp Concurrency">
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
<a-input-number v-model.number="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -465,7 +684,7 @@
</a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true">
<a-form-item style="margin: 10px 0">
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"></a-input>
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
</a-form-item>
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>

View File

@@ -6,6 +6,19 @@
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
@@ -16,8 +29,9 @@
<a-form-item label='Follow Redirect'>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
<a-form-item label='Timeout'>
<a-input-number v-model.number="inbound.settings.timeout" :min="0"></a-input-number>
</a-form-item>
</a-form>
<!-- sockopt -->
<template>
{{template "form/streamSockopt"}}
</template>
{{end}}

View File

@@ -1,21 +1,25 @@
{{define "form/http"}}
<a-form>
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small"
@click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
<a-form-item label="Allow Transparent">
<a-switch v-model="inbound.settings.allowTransparent" />
</a-form-item>
</a-form>
{{end}}

View File

@@ -45,6 +45,9 @@
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='ivCheck'>
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
</a-form-item>
</a-form>
<a-divider style="margin:0;"></a-divider>
{{end}}

View File

@@ -46,7 +46,7 @@
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>

View File

@@ -0,0 +1,44 @@
{{define "form/tun"}}
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.tun.nameDesc" }}</span>
</template>
Interface Name
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.name"
placeholder="xray0"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.tun.mtuDesc" }}</span>
</template>
MTU
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="inbound.settings.mtu" :min="1"
:max="9000" placeholder="1500"></a-input-number>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.tun.userLevelDesc" }}</span>
</template>
{{ i18n "pages.xray.tun.userLevel" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="inbound.settings.userLevel" :min="0"
placeholder="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View File

@@ -20,7 +20,29 @@
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Authentication">
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="decryption">
<a-input v-model.trim="inbound.settings.decryption"></a-input>
</a-form-item>
<a-form-item label="encryption">
<a-input v-model="inbound.settings.encryption"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-space>
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
<a-button danger @click="clearVlessEnc">Clear</a-button>
</a-space>
</a-form-item>
</a-form>
</template>
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks">
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
@@ -48,9 +70,38 @@
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>
</template>
<template v-if="inbound.canEnableVisionSeed()">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Vision Seed">
<a-row :gutter="8">
<a-col :span="6">
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900" @change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500" @change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900" @change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
</a-col>
<a-col :span="6">
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256" @change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
</a-col>
</a-row>
<a-space :size="8" :style="{ marginTop: '8px' }">
<a-button type="primary" @click="setRandomTestseed">
Rand
</a-button>
<a-button @click="resetTestseed">
Reset
</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider :style="{ margin: '5px 0' }"></a-divider>
</template>
{{end}}

View File

@@ -20,8 +20,8 @@
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
<a-form-item label='No Kernel Tun'>
<a-switch v-model="inbound.settings.noKernelTun"></a-switch>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>

View File

@@ -0,0 +1,61 @@
{{define "form/realitySettings"}}
<template>
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
Short IDs
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
</a-icon>
</template>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='Private Key'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='Public Key'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-space>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
<a-button danger @click="clearX25519Cert">Clear</a-button>
</a-space>
</a-form-item>
<a-form-item label="mldsa65 Seed">
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
</a-form-item>
<a-form-item label="mldsa65 Verify">
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
</a-form-item>
<a-form-item label=" ">
<a-space>
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
<a-button danger @click="clearMldsa65">Clear</a-button>
</a-space>
</a-form-item>
</template>
{{end}}

View File

@@ -1,9 +1,8 @@
{{define "form/sniffing"}}
<a-divider style="margin:0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<span slot="label">
Sniffing
{{ i18n "enabled" }}
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>

View File

@@ -17,10 +17,10 @@
</template>
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65535"></a-input-number>
</a-tooltip>
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
</a-form>
{{end}}
{{end}}

View File

@@ -0,0 +1,69 @@
{{define "form/streamFinalMask"}}
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item label="UDP Masks">
<a-button icon="plus" type="primary" size="small"
@click="inbound.stream.addUdpMask(inbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns')"></a-button>
</a-form-item>
<template
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp"
:key="index" :colon="false"
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
<a-icon type="delete"
@click="() => inbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
</a-divider>
<a-form-item label='Type'>
<a-select v-model="mask.type"
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-aes128gcm">
mKCP AES-128-GCM</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dns">
Header DNS</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-dtls">
Header DTLS 1.2</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-srtp">
Header SRTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-utp">
Header uTP</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wechat">
Header WeChat Video</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="header-wireguard">
Header WireGuard</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="mkcp-original">
mKCP Original</a-select-option>
<a-select-option
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
value="xdns">
xDNS (Experimental)</a-select-option>
<a-select-option v-if="inbound.stream.network === 'kcp'"
value="xicmp">
xICMP (Experimental)</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Password'
v-if="['mkcp-aes128gcm'].includes(mask.type)">
<a-input v-model.trim="mask.settings.password"
placeholder="Obfuscation password"></a-input>
</a-form-item>
<a-form-item label='Domain'
v-if="['header-dns', 'xdns'].includes(mask.type)">
<a-input v-model.trim="mask.settings.domain"
placeholder="e.g., www.example.com"></a-input>
</a-form-item>
</a-form>
</template>
</a-form>
{{end}}

View File

@@ -1,19 +0,0 @@
{{define "form/streamHTTP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.http.path"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">{{ i18n "host" }}
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
</template>
<template v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]">
<a-button size="small" slot="addonAfter"
@click="inbound.stream.http.removeHost(index)"
v-if="inbound.stream.http.host.length>1">-</a-button>
</a-input>
</template>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,48 +1,32 @@
{{define "form/streamKCP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576"
:max="1460"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10"
:max="100"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.downCap"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"
:min="0"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"
:min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,33 +0,0 @@
{{define "form/streamQUIC"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,17 +1,15 @@
{{define "form/streamSettings"}}
<!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="{{ i18n "transmission" }}">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="tcp">TCP (RAW)</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP/2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option>
<a-select-option value="splithttp">SplitHTTP</a-select-option>
<a-select-option value="xhttp">XHTTP</a-select-option>
</a-select>
</a-form-item>
</a-form>
@@ -31,16 +29,6 @@
{{template "form/streamWS"}}
</template>
<!-- http -->
<template v-if="inbound.stream.network === 'http'">
{{template "form/streamHTTP"}}
</template>
<!-- quic -->
<template v-if="inbound.stream.network === 'quic'">
{{template "form/streamQUIC"}}
</template>
<!-- grpc -->
<template v-if="inbound.stream.network === 'grpc'">
{{template "form/streamGRPC"}}
@@ -51,9 +39,15 @@
{{template "form/streamHTTPUPGRADE"}}
</template>
<!-- splithttp -->
<template v-if="inbound.stream.network === 'splithttp'">
{{template "form/streamSplitHTTP"}}
<!-- xhttp -->
<template v-if="inbound.stream.network === 'xhttp'">
{{template "form/streamXHTTP"}}
</template>
<!-- finalmask - only for TCP, WS, HTTPUpgrade, XHTTP, mKCP -->
<template
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)">
{{template "form/streamFinalMask"}}
</template>
<!-- sockopt -->

View File

@@ -1,24 +1,73 @@
{{define "form/streamSockopt"}}
<a-divider style="margin:0;"></a-divider>
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="TPROXY">
<a-form-item label="Sockopt">
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
</a-form-item>
<template v-if="inbound.stream.sockoptSwitch">
<a-form-item label="PROXY Protocol">
<a-form-item label="Route Mark">
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Keep Alive Interval">
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Keep Alive Idle">
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Max Seg">
<a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP User Timeout">
<a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP Window Clamp">
<a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="Proxy Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
<a-form-item label="Multipath TCP">
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item>
<a-form-item label="TPROXY">
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label="Penetrate">
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
</a-form-item>
<a-form-item label="V6 Only">
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
</a-form-item>
<a-form-item label='Domain Strategy'>
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='TCP Congestion'>
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="TProxy">
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">Off</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">TPROXY</a-select-option>
<a-select-option value="tproxy">TProxy</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Dialer Proxy">
<a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
</a-form-item>
<a-form-item label="Interface Name">
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
</a-form-item>
<a-form-item label="Trusted X-Forwarded-For">
<a-select mode="tags" v-model="inbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
</a-select>
</a-form-item>
</template>

View File

@@ -1,29 +0,0 @@
{{define "form/streamSplitHTTP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.splithttp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.splithttp.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item label="Max Upload Size (MB)">
<a-input-number v-model="inbound.stream.splithttp.maxUploadSize" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="Max Concurrent Upload">
<a-input-number v-model="inbound.stream.splithttp.maxConcurrentUploads" :min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View File

@@ -0,0 +1,135 @@
{{define "form/streamXHTTP"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
<a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item label='Mode'>
<a-select v-model="inbound.stream.xhttp.mode" style="width: 50%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
<a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
</a-form-item>
<a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
<a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
</a-form-item>
<a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
<a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
</a-form-item>
<a-form-item label="Padding Bytes">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
</a-form-item>
<a-form-item label="Padding Obfs Mode">
<a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
</a-form-item>
<template v-if="inbound.stream.xhttp.xPaddingObfsMode">
<a-form-item label="Padding Key">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingKey"
placeholder="x_padding"></a-input>
</a-form-item>
<a-form-item label="Padding Header">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader"
placeholder="X-Padding"></a-input>
</a-form-item>
<a-form-item label="Padding Placement">
<a-select v-model="inbound.stream.xhttp.xPaddingPlacement"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (queryInHeader)</a-select-option>
<a-select-option
value="queryInHeader">queryInHeader</a-select-option>
<a-select-option value="header">header</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Padding Method">
<a-select v-model="inbound.stream.xhttp.xPaddingMethod"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (repeat-x)</a-select-option>
<a-select-option value="repeat-x">repeat-x</a-select-option>
<a-select-option value="tokenish">tokenish</a-select-option>
</a-select>
</a-form-item>
</template>
<a-form-item label="Uplink HTTP Method">
<a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (POST)</a-select-option>
<a-select-option value="POST">POST</a-select-option>
<a-select-option value="PUT">PUT</a-select-option>
<a-select-option value="GET">GET (packet-up only)</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Session Placement">
<a-select v-model="inbound.stream.xhttp.sessionPlacement"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (path)</a-select-option>
<a-select-option value="path">path</a-select-option>
<a-select-option value="header">header</a-select-option>
<a-select-option value="cookie">cookie</a-select-option>
<a-select-option value="query">query</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Session Key"
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
<a-input v-model.trim="inbound.stream.xhttp.sessionKey"
placeholder="x_session"></a-input>
</a-form-item>
<a-form-item label="Sequence Placement">
<a-select v-model="inbound.stream.xhttp.seqPlacement"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (path)</a-select-option>
<a-select-option value="path">path</a-select-option>
<a-select-option value="header">header</a-select-option>
<a-select-option value="cookie">cookie</a-select-option>
<a-select-option value="query">query</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Sequence Key"
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
<a-input v-model.trim="inbound.stream.xhttp.seqKey"
placeholder="x_seq"></a-input>
</a-form-item>
<a-form-item label="Uplink Data Placement"
v-if="inbound.stream.xhttp.mode === 'packet-up'">
<a-select v-model="inbound.stream.xhttp.uplinkDataPlacement"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value>Default (body)</a-select-option>
<a-select-option value="body">body</a-select-option>
<a-select-option value="header">header</a-select-option>
<a-select-option value="query">query</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Uplink Data Key"
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
<a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey"
placeholder="x_data"></a-input>
</a-form-item>
<a-form-item label="Uplink Chunk Size"
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
<a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize"
:min="0" placeholder="0 (unlimited)"></a-input-number>
</a-form-item>
<a-form-item label="No SSE Header">
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
</a-form-item>
</a-form>
{{end}}

View File

@@ -10,126 +10,113 @@
</a-radio-group>
</a-form-item>
<!-- tls settings -->
<template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item>
<a-form-item label="Cipher Suites">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">Auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Min/Max Version">
<a-input-group compact>
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<!-- tls settings -->
<template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item>
<a-form-item label="Cipher Suites">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">Auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
</a-select>
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint"
</a-form-item>
<a-form-item label="Min/Max Version">
<a-input-group compact>
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select
mode="multiple"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="Reject Unknown SNI">
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
<a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="Reject Unknown SNI">
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
<a-form-item label="Disable System Root">
<a-switch v-model="inbound.stream.tls.disableSystemRoot"></a-switch>
</a-form-item>
<a-form-item label="Session Resumption">
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
</a-form-item>
<a-divider :style="{ margin: '0' }"></a-divider>
<template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</template>
<a-form-item label='ECH key'>
<a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
</a-form-item>
<a-form-item label='ECH config'>
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
</a-form-item>
<a-form-item label='ECH force query'>
<a-select v-model="inbound.stream.tls.echForceQuery" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
<a-space>
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
<a-button danger @click="clearEchCert">Clear</a-button>
</a-space>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</template>
</template>
<!-- reality settings -->
<template v-if="inbound.stream.isReality">
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
Short IDs
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
</a-icon>
</template>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label='Private Key'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='Public Key'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
</a-form-item>
</template>
<!-- reality settings -->
<template v-if="inbound.stream.isReality">
{{template "form/realitySettings"}}
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -25,7 +25,7 @@
<tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
</tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade || inbound.isSplithttp">
<template v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host">
@@ -44,18 +44,14 @@
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
</tr>
</template>
<template v-if="inbound.isQuic">
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isXHTTP">
<tr>
<td>Mode</td>
<td>
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
</td>
</tr>
</template>
<template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td>
<a-tooltip :title="[[ inbound.serviceName ]]">
@@ -70,9 +66,18 @@
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br />
<td>Authentication</td>
<a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ?
inbound.settings.selectedAuth : '' ]]</a-tag>
<br />
{{ i18n "encryption" }}
<a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ?
inbound.settings.encryption : '' ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : ''
]]</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
@@ -284,8 +289,8 @@
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>Kernel Mode</td>
<td>[[ inbound.settings.kernelMode ]]</td>
<td>No Kernel Tun</td>
<td>[[ inbound.settings.noKernelTun ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>
@@ -412,8 +417,8 @@
},
},
methods: {
copyToClipboard(elmentId,content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
copyToClipboard(elementId,content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {
@@ -433,4 +438,4 @@
});
</script>
{{end}}
{{end}}

View File

@@ -26,6 +26,11 @@
} else {
this.inbound = new Inbound();
}
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed) || this.inbound.settings.testseed.length < 4) {
this.inbound.settings.testseed = [900, 500, 900, 256].slice();
}
}
if (dbInbound) {
this.dbInbound = new DBInbound(dbInbound);
} else {
@@ -42,6 +47,27 @@
loading(loading=true) {
inModal.confirmLoading = loading;
},
updateTestseed(index, value) {
if (!inModal.inbound || !inModal.inbound.settings) return;
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed)) {
inModal.inbound.settings.testseed = [900, 500, 900, 256];
}
while (inModal.inbound.settings.testseed.length <= index) {
inModal.inbound.settings.testseed.push(0);
}
inModal.inbound.settings.testseed[index] = value;
},
setRandomTestseed() {
if (!inModal.inbound || !inModal.inbound.settings) return;
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4) {
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
}
inModal.inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
},
resetTestseed() {
if (!inModal.inbound || !inModal.inbound.settings) return;
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
}
};
new Vue({
@@ -124,7 +150,7 @@
},
async getNewX25519Cert(){
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewX25519Cert');
const msg = await HttpUtil.get('/server/getNewX25519Cert');
inModal.loading(false);
if (!msg.success) {
return;
@@ -132,6 +158,80 @@
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
},
clearX25519Cert() {
this.inbound.stream.reality.privateKey = '';
this.inbound.stream.reality.settings.publicKey = '';
},
async getNewmldsa65() {
inModal.loading(true);
const msg = await HttpUtil.get('/server/getNewmldsa65');
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
},
clearMldsa65() {
this.inbound.stream.reality.mldsa65Seed = '';
this.inbound.stream.reality.settings.mldsa65Verify = '';
},
async getNewEchCert() {
inModal.loading(true);
const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
inModal.loading(false);
if (!msg.success) {
return;
}
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
},
clearEchCert() {
this.inbound.stream.tls.echServerKeys = '';
this.inbound.stream.tls.settings.echConfigList = '';
},
async getNewVlessEnc() {
inModal.loading(true);
const msg = await HttpUtil.get('/server/getNewVlessEnc');
inModal.loading(false);
if (!msg.success) {
return;
}
const auths = msg.obj.auths || [];
const selected = inModal.inbound.settings.selectedAuth;
const block = auths.find(a => a.label === selected);
if (!block) {
console.error("No auth block for", selected);
return;
}
inModal.inbound.settings.decryption = block.decryption;
inModal.inbound.settings.encryption = block.encryption;
},
clearVlessEnc() {
this.inbound.settings.decryption = 'none';
this.inbound.settings.encryption = 'none';
this.inbound.settings.selectedAuth = undefined;
},
updateTestseed(index, value) {
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed)) {
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
}
while (this.inbound.settings.testseed.length <= index) {
this.inbound.settings.testseed.push(0);
}
this.$set(this.inbound.settings.testseed, index, value);
},
setRandomTestseed() {
const newSeed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
this.$set(this.inbound.settings, 'testseed', newSeed);
},
resetTestseed() {
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
}
},
});

View File

@@ -451,7 +451,7 @@
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}}
<script>
@@ -925,7 +925,11 @@
protocol: inbound.protocol,
settings: inbound.settings.toString(),
};
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString();
await this.submit('/xui/inbound/add', data, inModal);
@@ -944,7 +948,11 @@
protocol: inbound.protocol,
settings: inbound.settings.toString(),
};
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString();
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);

View File

@@ -296,6 +296,7 @@
<a-select-option value="20">20</a-select-option>
<a-select-option value="50">50</a-select-option>
<a-select-option value="100">100</a-select-option>
<a-select-option value="500">500</a-select-option>
</a-select>
<a-select v-model="logModal.level" style="width:100px;"
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -534,7 +535,7 @@
this.loadingTip = tip;
},
async getStatus() {
const msg = await HttpUtil.post('/server/status');
const msg = await HttpUtil.get('/server/status');
if (msg.success) {
this.setStatus(msg.obj);
}
@@ -544,7 +545,7 @@
},
async openSelectV2rayVersion() {
this.loading(true);
const msg = await HttpUtil.post('server/getXrayVersion');
const msg = await HttpUtil.get('server/getXrayVersion');
this.loading(false);
if (!msg.success) {
return;
@@ -594,7 +595,7 @@
},
async openConfig() {
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
const msg = await HttpUtil.get('server/getConfigJson');
this.loading(false);
if (!msg.success) {
return;

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@
<td>[[ warpModal.warpData.private_key ]]</td>
</tr>
</table>
<a-button @click="delConfig" :loading="warpModal.confirmLoading" type="danger">{{ i18n "delete" }}</a-button>
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
<a-collapse style="margin: 10px 0;">
<a-collapse-panel header='WARP/WARP+ License Key'>
@@ -91,114 +92,153 @@
</a-modal>
<script>
const warpModal = {
visible: false,
confirmLoading: false,
warpData: null,
warpConfig: null,
warpOutbound: null,
show() {
this.visible = true;
this.warpConfig = null;
this.getData();
const warpModal = {
visible: false,
confirmLoading: false,
warpData: null,
warpConfig: null,
warpOutbound: null,
show() {
this.visible = true;
this.warpConfig = null;
this.getData();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading=true) {
this.confirmLoading = loading;
},
async getData(){
this.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/data');
this.loading(false);
if (msg.success) {
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
}
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#warp-modal',
data: {
warpModal: warpModal,
warpPlus: '',
},
methods: {
collectConfig() {
config = warpModal.warpConfig.config;
peer = config.peers[0];
if(config){
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: this.getAddresses(config.interface.addresses),
reserved: this.getResolved(config.client_id),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
noKernelTun: false
}
});
}
},
close() {
this.visible = false;
this.loading(false);
getAddresses(addrs){
let addresses = [];
if (addrs.v4) addresses.push(addrs.v4 + "/32");
if (addrs.v6) addresses.push(addrs.v6 + "/128");
return addresses;
},
loading(loading=true) {
this.confirmLoading = loading;
getResolved(client_id){
let reserved = [];
let decoded = atob(client_id);
let hexString = '';
for (let i = 0; i < decoded.length; i++) {
let hex = decoded.charCodeAt(i).toString(16);
hexString += (hex.length === 1 ? '0' : '') + hex;
}
for (let i = 0; i < hexString.length; i += 2) {
let hexByte = hexString.slice(i, i + 2);
let decValue = parseInt(hexByte, 16);
reserved.push(decValue);
}
return reserved;
},
async getData(){
this.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/data');
this.loading(false);
async register(){
warpModal.loading(true);
const keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
if (msg.success) {
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
const resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data;
warpModal.warpConfig = resp.config;
this.collectConfig();
}
warpModal.loading(false);
},
async updateLicense(l){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null;
this.warpPlus = '';
}
warpModal.loading(false);
},
async getConfig(){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/config');
warpModal.loading(false);
if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj);
this.collectConfig();
}
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#warp-modal',
data: {
warpModal: warpModal,
warpPlus: '',
},
methods: {
collectConfig() {
config = warpModal.warpConfig.config;
peer = config.peers[0];
if(config){
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: Object.values(config.interface.addresses),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
kernelMode: false
}
});
}
},
async register(){
warpModal.loading(true);
keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
if (msg.success) {
resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data;
warpModal.warpConfig = resp.config;
this.collectConfig();
}
warpModal.loading(false);
},
async updateLicense(l){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null;
this.warpPlus = '';
}
warpModal.loading(false);
},
async getConfig(){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/config');
warpModal.loading(false);
if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj);
this.collectConfig();
}
},
addOutbound(){
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
resetOutbound(){
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
async delConfig(){
warpModal.loading(true);
const msg = await HttpUtil.post('/xui/xray/warp/del');
warpModal.loading(false);
if (msg.success) {
warpModal.warpData = null;
warpModal.warpConfig = null;
this.delOutbound();
}
},
computed: {
warpOutboundIndex: {
get: function() {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
}
addOutbound(){
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
resetOutbound(){
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
delOutbound(){
if (this.warpOutboundIndex != -1){
app.templateSettings.outbounds.splice(this.warpOutboundIndex,1);
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
}
warpModal.close();
}
},
computed: {
warpOutboundIndex: {
get: function() {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
}
}
});
}
});
</script>
{{end}}

View File

@@ -160,41 +160,69 @@
</a-alert>
</a-row>
<a-list-item>
<a-row style="padding: 20px">
<a-row style="padding: 10px 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
description='{{ i18n "pages.xray.logLevelDesc" }}' />
description='{{ i18n "pages.xray.logLevelDesc" }}'>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="logLevel"
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="level in ['none', 'debug', 'info', 'warning', 'error']" :value="level">[[ level ]]</a-select-option>
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option v-for="s in log.loglevel" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
<a-row style="padding: 10px 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
description='{{ i18n "pages.xray.accessLogDesc" }}'>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option value=''>Empty</a-select-option>
<a-select-option v-for="s in log.access" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
<a-row style="padding: 10px 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
description='{{ i18n "pages.xray.errorLogDesc" }}'>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option value=''>Empty</a-select-option>
<a-select-option v-for="s in log.error" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
<a-row style="padding: 10px 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.maskAddress" }}'
description='{{ i18n "pages.xray.maskAddressDesc" }}'>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<a-select-option value=''>Empty</a-select-option>
<a-select-option v-for="s in log.maskAddress" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.dnsLog"}}' desc='{{ i18n "pages.xray.dnsLogDesc"}}'
v-model="dnslog"></setting-list-item>
</a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
description='{{ i18n "pages.xray.accessLogDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<a-input v-model="logAccess"></a-input>
</a-col>
</a-row>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
description='{{ i18n "pages.xray.errorLogDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<a-input v-model="logError"></a-input>
</a-col>
</a-row>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
@@ -206,69 +234,155 @@
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Torrent"}}' desc='{{ i18n "pages.xray.TorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.PrivateIp"}}' desc='{{ i18n "pages.xray.PrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Ads"}}' desc='{{ i18n "pages.xray.AdsDesc"}}' v-model="AdsSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Family"}}' desc='{{ i18n "pages.xray.FamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.blockCountryConfigs"}}'>
<a-collapse-panel header='{{ i18n "pages.xray.basicRouting"}}'>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.blockCountryConfigsDesc" }}
{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRIp"}}' desc='{{ i18n "pages.xray.IRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRDomain"}}' desc='{{ i18n "pages.xray.IRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaIp"}}' desc='{{ i18n "pages.xray.ChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaDomain"}}' desc='{{ i18n "pages.xray.ChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaIp"}}' desc='{{ i18n "pages.xray.RussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaDomain"}}' desc='{{ i18n "pages.xray.RussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.blockips" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" v-model="blockedIPs" style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.IPsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.blockdomains" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" style="width: 100%"
v-model="blockedDomains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.directCountryConfigsDesc" }}
{{ i18n "pages.xray.directConnectionsConfigsDesc" }}
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRIp"}}' desc='{{ i18n "pages.xray.DirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRDomain"}}' desc='{{ i18n "pages.xray.DirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaIp"}}' desc='{{ i18n "pages.xray.DirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaDomain"}}' desc='{{ i18n "pages.xray.DirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaIp"}}' desc='{{ i18n "pages.xray.DirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaDomain"}}' desc='{{ i18n "pages.xray.DirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.directips" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" style="width: 100%"
v-model="directIPs"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.IPsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.directdomains" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" style="width: 100%"
v-model="directDomains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.ipv4ConfigsDesc" }}
{{ i18n "pages.xray.ipv4RoutingDesc" }}
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.ipv4Routing" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" style="width: 100%"
v-model="ipv4Domains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.ServicesOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.warpConfigsDesc" }}
{{ i18n "pages.xray.warpRoutingDesc" }}
</template>
</a-alert>
</a-row>
<template v-if="WarpExist">
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleWARP"}}' desc='{{ i18n "pages.xray.GoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
<a-list-item>
<a-row style="padding: 0 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.warpRouting" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select mode="tags" style="width: 100%"
v-model="warpDomains"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p.value" :label="p.label"
v-for="p in settingsData.ServicesOptions"> [[ p.label ]]
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
</template>
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
</a-collapse-panel>
@@ -571,6 +685,9 @@
<template slot="domain" slot-scope="dns,index">
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
</template>
<template slot="expectIPs" slot-scope="dns,index">
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
</template>
</a-table>
<a-divider>Fake DNS</a-divider>
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
@@ -680,6 +797,7 @@
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
{ title: '{{ i18n "pages.xray.dns.expectIPs"}}', align: 'center', width: 50, scopedSlots: { customRender: 'expectIPs' } },
];
const fakednsColumns = [
@@ -738,41 +856,43 @@
tag: "direct",
protocol: "freedom"
},
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
routingDomainStrategies: ["AsIs", "IpIfNonMatch", "IpOnDemand"],
log: {
loglevel: ["none", "debug", "info", "warning", "error"],
access: ["none", "./access.log"],
error: ["none", "./error.log"],
dnsLog: false,
maskAddress: ["quarter", "half", "full"],
},
settingsData: {
protocols: {
bittorrent: ["bittorrent"],
},
ips: {
local: ["geoip:private"],
cn: ["geoip:cn"],
ir: ["ext:geoip_IR.dat:ir"],
ru: ["geoip:ru"],
},
domains: {
ads: [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
],
openai: ["geosite:openai"],
google: ["geosite:google"],
spotify: ["geosite:spotify"],
netflix: ["geosite:netflix"],
meta: ["geosite:meta"],
cn: [
"geosite:cn",
"regexp:.*\\.cn$"
],
ru: [
"geosite:category-gov-ru",
"regexp:.*\\.ru$"
],
ir: [
"regexp:.*\\.ir$",
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
"ext:geosite_IR.dat:ir"
]
},
IPsOptions: [
{ label: 'Private IP', value: 'geoip:private' },
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
{ label: '🇨🇳 China', value: 'geoip:cn' },
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
],
DomainsOptions: [
{ label: 'Ads All', value: 'geosite:category-ads-all' },
{ label: 'Ads IR', value: 'ext:geosite_IR.dat:category-ads-all' },
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
{ label: '🇨🇳 China', value: 'geosite:cn' },
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
],
ServicesOptions: [
{ label: 'Apple', value: 'geosite:apple' },
{ label: 'Meta', value: 'geosite:meta' },
{ label: 'Google', value: 'geosite:google' },
{ label: 'OpenAI', value: 'geosite:openai' },
{ label: 'Spotify', value: 'geosite:spotify' },
{ label: 'Netflix', value: 'geosite:netflix' },
],
familyProtectDNS: {
"servers": [
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
@@ -937,12 +1057,13 @@
});
},
changeObsCode() {
if (this.obsSettings == ''){
return
}
if(this.cm != null) {
this.cm.toTextArea();
}
if (this.obsSettings == ''){
this.cm = null;
return
}
textAreaObj = document.getElementById('obsSetting');
textAreaObj.value = this[this.obsSettings];
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
@@ -966,7 +1087,9 @@
switch(o.protocol){
case Protocols.VMess:
case Protocols.VLESS:
serverObj = o.settings.vnext;
if (o.settings && o.settings.address && o.settings.port) {
return [o.settings.address + ':' + o.settings.port];
}
break;
case Protocols.HTTP:
case Protocols.Socks:
@@ -1116,7 +1239,8 @@
balancer: {
tag: '',
strategy: 'random',
selector: []
selector: [],
fallbackTag: ''
},
confirm: (balancer) => {
balancerModal.loading();
@@ -1126,27 +1250,17 @@
}
let tmpBalancer = {
'tag': balancer.tag,
'selector': balancer.selector
'selector': balancer.selector,
'fallbackTag': balancer.fallbackTag
};
if (balancer.strategy && balancer.strategy != 'random') {
tmpBalancer.strategy = {
'type': balancer.strategy
};
if (balancer.strategy == 'leastPing'){
if (!newTemplateSettings.observatory)
newTemplateSettings.observatory = this.defaultObservatory;
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
}
if (balancer.strategy == 'leastLoad'){
if (!newTemplateSettings.burstObservatory)
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
}
}
newTemplateSettings.routing.balancers.push(tmpBalancer);
this.templateSettings = newTemplateSettings;
this.updateObservatorySelectors();
balancerModal.close();
this.changeObsCode();
},
@@ -1166,7 +1280,8 @@
let tmpBalancer = {
'tag': balancer.tag,
'selector': balancer.selector
'selector': balancer.selector,
'fallbackTag': balancer.fallbackTag
};
// Remove old tag
@@ -1181,18 +1296,6 @@
tmpBalancer.strategy = {
'type': balancer.strategy
};
if (balancer.strategy == 'leastPing'){
if (!newTemplateSettings.observatory)
newTemplateSettings.observatory = this.defaultObservatory;
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
}
if (balancer.strategy == 'leastLoad'){
if (!newTemplateSettings.burstObservatory)
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
}
}
newTemplateSettings.routing.balancers[index] = tmpBalancer;
@@ -1205,12 +1308,50 @@
});
}
this.templateSettings = newTemplateSettings;
this.updateObservatorySelectors();
balancerModal.close();
this.changeObsCode();
},
isEdit: true
});
},
updateObservatorySelectors(){
newTemplateSettings = this.templateSettings;
const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
const leastLoads = this.balancersData.filter((b) =>
b.strategy === 'leastLoad' ||
b.strategy === 'roundRobin' ||
b.strategy === 'random'
);
if (leastPings.length>0){
if (!newTemplateSettings.observatory)
newTemplateSettings.observatory = this.defaultObservatory;
newTemplateSettings.observatory.subjectSelector = [];
leastPings.forEach((b) => {
b.selector.forEach((s) => {
if (!newTemplateSettings.observatory.subjectSelector.includes(s))
newTemplateSettings.observatory.subjectSelector.push(s);
});
});
} else {
delete newTemplateSettings.observatory
}
if (leastLoads.length>0){
if (!newTemplateSettings.burstObservatory)
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
newTemplateSettings.burstObservatory.subjectSelector = [];
leastLoads.forEach((b) => {
b.selector.forEach((s) => {
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
newTemplateSettings.burstObservatory.subjectSelector.push(s);
});
});
} else {
delete newTemplateSettings.burstObservatory
}
this.templateSettings = newTemplateSettings;
this.changeObsCode();
},
deleteBalancer(index) {
newTemplateSettings = this.templateSettings;
@@ -1221,19 +1362,13 @@
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
newTemplateSettings.routing.balancers.splice(realIndex, 1);
// Remove tag from observatory
if (newTemplateSettings.observatory){
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != removedBalancer.tag);
}
if (newTemplateSettings.burstObservatory){
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != removedBalancer.tag);
}
// Update balancers property to an empty array if there are no more balancers
if (newTemplateSettings.routing.balancers.length === 0) {
delete newTemplateSettings.routing.balancers;
}
this.templateSettings = newTemplateSettings;
this.updateObservatorySelectors();
this.obsSettings = '';
this.changeObsCode()
},
addDNSServer(){
@@ -1366,28 +1501,59 @@
},
},
logLevel: {
get: function () { return this.logSettings?.loglevel?? 'none'; },
set: function (newValue) {
newLogSettings = this.logSettings;
newLogSettings.loglevel = newValue;
this.logSettings = newLogSettings;
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
return this.templateSettings.log.loglevel;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.loglevel = newValue;
this.templateSettings = newTemplateSettings;
}
},
logAccess: {
get: function () { return this.logSettings?.access?? ''; },
set: function (newValue) {
newLogSettings = this.logSettings;
newValue == "" ? delete newLogSettings.access : newLogSettings.access = newValue;
this.logSettings = newLogSettings;
accessLog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
return this.templateSettings.log.access;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.access = newValue;
this.templateSettings = newTemplateSettings;
}
},
logError: {
get: function () { return this.logSettings?.error?? ''; },
set: function (newValue) {
newLogSettings = this.logSettings;
newValue == "" ? delete newLogSettings.error : newLogSettings.error = newValue;
this.logSettings = newLogSettings;
errorLog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
return this.templateSettings.log.error;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.error = newValue;
this.templateSettings = newTemplateSettings;
}
},
dnslog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.dnsLog) return false;
return this.templateSettings.log.dnsLog;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.dnsLog = newValue;
this.templateSettings = newTemplateSettings;
}
},
maskAddressLog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
return this.templateSettings.log.maskAddress;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.maskAddress = newValue;
this.templateSettings = newTemplateSettings;
}
},
inboundSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
@@ -1472,7 +1638,8 @@
'key': index,
'tag': o.tag ? o.tag : "",
'strategy': o.strategy?.type ?? "random",
'selector': o.selector ? o.selector : []
'selector': o.selector ? o.selector : [],
'fallbackTag': o.fallbackTag?? '',
});
});
}
@@ -1499,22 +1666,8 @@
this.templateSettings = newTemplateSettings;
},
},
observatoryEnable: {
get: function () { return this.templateSettings != null && this.templateSettings.observatory },
set: function (v) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.observatory = v ? this.defaultObservatory : undefined;
this.templateSettings = newTemplateSettings;
}
},
burstObservatoryEnable: {
get: function () { return this.templateSettings != null && this.templateSettings.burstObservatory },
set: function (v) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.burstObservatory = v ? this.defaultBurstObservatory : undefined;
this.templateSettings = newTemplateSettings;
}
},
observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
freedomStrategy: {
get: function () {
if (!this.templateSettings) return "AsIs";
@@ -1618,30 +1771,6 @@
}
},
},
privateIpSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
},
set: function (newValue) {
if (newValue) {
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
} else {
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
}
},
},
AdsSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
}
},
},
familyProtectSettings: {
get: function () {
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
@@ -1657,239 +1786,11 @@
this.templateSettings = newTemplateSettings;
},
},
GoogleIPv4Settings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
},
set: function (newValue) {
if (newValue) {
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
} else {
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
}
},
},
NetflixIPv4Settings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
},
set: function (newValue) {
if (newValue) {
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
} else {
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
}
},
},
IRIpSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
},
set: function (newValue) {
if (newValue) {
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
} else {
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
}
}
},
IRDomainSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
}
}
},
ChinaIpSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
},
set: function (newValue) {
if (newValue) {
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
} else {
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
}
}
},
ChinaDomainSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
}
}
},
RussiaIpSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
},
set: function (newValue) {
if (newValue) {
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
} else {
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
}
}
},
RussiaDomainSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
}
}
},
IRIpDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
},
set: function (newValue) {
if (newValue) {
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
} else {
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
}
}
},
IRDomainDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
},
set: function (newValue) {
if (newValue) {
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
} else {
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
}
}
},
ChinaIpDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
},
set: function (newValue) {
if (newValue) {
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
} else {
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
}
}
},
ChinaDomainDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
},
set: function (newValue) {
if (newValue) {
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
} else {
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
}
}
},
RussiaIpDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
},
set: function (newValue) {
if (newValue) {
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
} else {
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
}
}
},
RussiaDomainDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
},
set: function (newValue) {
if (newValue) {
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
} else {
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
}
}
},
WarpExist: {
get: function() {
return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0 : false;
},
},
GoogleWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
}
},
},
OpenAIWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
}
},
},
NetflixWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
}
},
},
MetaWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.meta, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.meta];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.meta.includes(data));
}
},
},
SpotifyWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
},
set: function (newValue) {
if (newValue) {
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
} else {
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
}
},
},
enableDNS: {
get: function () {
return this.templateSettings ? this.templateSettings.dns != null : false;

View File

@@ -31,6 +31,12 @@
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.fallback" }}'>
<a-select v-model="balancerModal.balancer.fallbackTag" clearable
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in [ '', ...balancerModal.outboundTags]" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
@@ -47,7 +53,8 @@
balancer: {
tag: '',
strategy: 'random',
selector: []
selector: [],
fallbackTag: ''
},
outboundTags: [],
balancerTags:[],
@@ -70,7 +77,8 @@
balancerModal.balancer = {
tag: '',
strategy: 'random',
selector: []
selector: [],
fallbackTag: ''
};
}
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);

View File

@@ -220,13 +220,13 @@
newRule = {};
rule.type = "field";
rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
rule.domain = value.domain.length>0 ? value.domain.split(',').map(s => s.trim()) : [];
rule.ip = value.ip.length>0 ? value.ip.split(',').map(s => s.trim()) : [];
rule.port = value.port;
rule.sourcePort = value.sourcePort;
rule.network = value.network;
rule.source = value.source.length>0 ? value.source.split(',') : [];
rule.user = value.user.length>0 ? value.user.split(',') : [];
rule.source = value.source.length>0 ? value.source.split(',').map(s => s.trim()) : [];
rule.user = value.user.length>0 ? value.user.split(',').map(s => s.trim()) : [];
rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);

View File

@@ -4,7 +4,7 @@ import (
"strconv"
"time"
"x-ui/web/service"
"github.com/alireza0/x-ui/web/service"
"github.com/shirou/gopsutil/v4/cpu"
)
@@ -20,7 +20,11 @@ func NewCheckCpuJob() *CheckCpuJob {
// Here run is a interface method of Job interface
func (j *CheckCpuJob) Run() {
threshold, _ := j.settingService.GetTgCpu()
threshold, err := j.settingService.GetTgCpu()
if err != nil || threshold <= 0 {
// If threshold cannot be retrieved or is not set, skip sending notifications
return
}
// get latest status of server
percent, err := cpu.Percent(1*time.Second, false)

View File

@@ -1,6 +1,6 @@
package job
import "x-ui/web/service"
import "github.com/alireza0/x-ui/web/service"
type CheckXrayRunningJob struct {
xrayService service.XrayService

View File

@@ -1,7 +1,7 @@
package job
import (
"x-ui/web/service"
"github.com/alireza0/x-ui/web/service"
)
type LoginStatus byte

View File

@@ -1,8 +1,8 @@
package job
import (
"x-ui/logger"
"x-ui/web/service"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/web/service"
)
type XrayTrafficJob struct {

View File

@@ -5,7 +5,7 @@ import (
"io/fs"
"strings"
"x-ui/logger"
"github.com/alireza0/x-ui/logger"
"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
@@ -48,10 +48,10 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
return nil
}
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
func createTemplateData(params []string, separator ...string) map[string]interface{} {
var sep string = "=="
if len(seperator) > 0 {
sep = seperator[0]
if len(separator) > 0 {
sep = separator[0]
}
templateData := make(map[string]interface{})

View File

@@ -3,13 +3,17 @@ package middleware
import (
"net"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
return func(c *gin.Context) {
host, _, _ := net.SplitHostPort(c.Request.Host)
host := c.Request.Host
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
host, _, _ = net.SplitHostPort(c.Request.Host)
}
if host != domain {
c.AbortWithStatus(http.StatusForbidden)

View File

@@ -1,34 +1,44 @@
{
"log": {
"loglevel": "warning"
},
"api": {
"tag": "api",
"services": ["HandlerService", "LoggerService", "StatsService"]
"services": [
"HandlerService",
"LoggerService",
"StatsService"
],
"tag": "api"
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
},
"tag": "api"
}
],
"log": {
"access": "none",
"dnsLog": false,
"error": "",
"loglevel": "warning",
"maskAddress": ""
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
"domainStrategy": "UseIP",
"noises": [],
"redirect": ""
},
"tag": "direct"
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
"settings": {},
"tag": "blocked"
}
],
"policy": {
@@ -47,19 +57,25 @@
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
"inboundTag": [
"api"
],
"outboundTag": "api",
"type": "field"
},
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "blocked",
"ip": ["geoip:private"]
"type": "field"
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
"protocol": [
"bittorrent"
],
"type": "field"
}
]
},

View File

@@ -6,11 +6,11 @@ import (
"strings"
"time"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/xray"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/xray"
"gorm.io/gorm"
)
@@ -19,6 +19,10 @@ type InboundService struct {
xrayApi xray.XrayAPI
}
const (
safeBatchSize = 500
)
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
@@ -98,8 +102,9 @@ func (s *InboundService) getAllEmails() ([]string, error) {
}
func (s *InboundService) contains(slice []string, str string) bool {
lowerStr := strings.ToLower(str)
for _, s := range slice {
if s == str {
if strings.ToLower(s) == lowerStr {
return true
}
}
@@ -518,11 +523,13 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
interfaceClients := settings["clients"].([]interface{})
var newClients []interface{}
needApiDel := false
for _, client := range interfaceClients {
c := client.(map[string]interface{})
c_id := c[client_key].(string)
if c_id == clientId {
email = c["email"].(string)
email, _ = c["email"].(string)
needApiDel, _ = c["enable"].(bool)
} else {
newClients = append(newClients, client)
}
@@ -541,23 +548,36 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
oldInbound.Settings = string(newSettings)
db := database.GetDB()
err = s.DelClientStat(db, email)
if err != nil {
logger.Error("Delete stats Data Error")
return false, err
}
needRestart := false
if len(email) > 0 {
s.xrayApi.Init(p.GetAPIPort())
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
if err1 == nil {
logger.Debug("Client deleted by api:", email)
needRestart = false
} else {
logger.Debug("Unable to del client by api:", err1)
needRestart = true
notDepleted := true
err = db.Model(xray.ClientTraffic{}).Select("enable").Where("email = ?", email).First(&notDepleted).Error
if err != nil {
logger.Error("Get stats error")
return false, err
}
err = s.DelClientStat(db, email)
if err != nil {
logger.Error("Delete stats Data Error")
return false, err
}
if needApiDel && notDepleted {
s.xrayApi.Init(p.GetAPIPort())
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
if err1 == nil {
logger.Debug("Client deleted by api:", email)
needRestart = false
} else {
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) {
logger.Debug("User is already deleted. Nothing to do more...")
} else {
logger.Debug("Error in deleting client by api:", err1)
needRestart = true
}
}
s.xrayApi.Close()
}
s.xrayApi.Close()
}
return needRestart, db.Save(oldInbound).Error
}
@@ -574,7 +594,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
inerfaceClients := settings["clients"].([]interface{})
interfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id)
if err != nil {
@@ -629,7 +649,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
settingsClients := oldSettings["clients"].([]interface{})
settingsClients[clientIndex] = inerfaceClients[0]
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
@@ -667,12 +687,18 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
needRestart := false
if len(oldEmail) > 0 {
s.xrayApi.Init(p.GetAPIPort())
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
if err1 == nil {
logger.Debug("Old client deleted by api:", clients[0].Email)
} else {
logger.Debug("Error in deleting client by api:", err1)
needRestart = true
if oldClients[clientIndex].Enable {
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
if err1 == nil {
logger.Debug("Old client deleted by api:", oldEmail)
} else {
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) {
logger.Debug("User is already deleted. Nothing to do more...")
} else {
logger.Debug("Error in deleting client by api:", err1)
needRestart = true
}
}
}
if clients[0].Enable {
cipher := ""
@@ -767,6 +793,11 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
return nil
}
type newExpiryTime struct {
Email string
NewExpiryTime int64
}
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
if len(traffics) == 0 {
// Empty onlineUsers
@@ -783,9 +814,18 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
emails = append(emails, traffic.Email)
}
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
if err != nil {
return err
for i := 0; i < len(emails); i += safeBatchSize {
end := i + safeBatchSize
if end > len(emails) {
end = len(emails)
}
batchClientTraffics := make([]*xray.ClientTraffic, 0, end-i)
err = tx.Model(xray.ClientTraffic{}).Where("email IN ?", emails[i:end]).Find(&batchClientTraffics).Error
if err != nil {
return err
}
dbClientTraffics = append(dbClientTraffics, batchClientTraffics...)
}
// Avoid empty slice error
@@ -793,7 +833,20 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
return nil
}
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
inboundExpiryTimeMap := make(map[int][]newExpiryTime, 0)
for index, t := range dbClientTraffics {
if t.ExpiryTime < 0 {
newClientExpiryTime := (time.Now().Unix() * 1000) - int64(t.ExpiryTime)
newExpiryTime := newExpiryTime{
Email: t.Email,
NewExpiryTime: newClientExpiryTime,
}
inboundExpiryTimeMap[t.InboundId] = append(inboundExpiryTimeMap[t.InboundId], newExpiryTime)
dbClientTraffics[index].ExpiryTime = newClientExpiryTime
}
}
err = s.adjustTraffics(tx, inboundExpiryTimeMap)
if err != nil {
return err
}
@@ -814,66 +867,90 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
}
// Set onlineUsers
p.SetOnlineClients(onlineClients)
if p != nil {
p.SetOnlineClients(onlineClients)
}
err = tx.Save(dbClientTraffics).Error
if err != nil {
logger.Warning("AddClientTraffic update data ", err)
for i := 0; i < len(dbClientTraffics); i += safeBatchSize {
end := i + safeBatchSize
if end > len(dbClientTraffics) {
end = len(dbClientTraffics)
}
err = tx.Save(dbClientTraffics[i:end]).Error
if err != nil {
logger.Warning("AddClientTraffic update data ", err)
return nil
}
}
return nil
}
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
inboundIds := make([]int, 0, len(dbClientTraffics))
for _, dbClientTraffic := range dbClientTraffics {
if dbClientTraffic.ExpiryTime < 0 {
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
func (s *InboundService) adjustTraffics(tx *gorm.DB, inboundExpiryTimeMap map[int][]newExpiryTime) error {
if len(inboundExpiryTimeMap) == 0 {
return nil
}
inboundIds := make([]int, 0)
for inId := range inboundExpiryTimeMap {
inboundIds = append(inboundIds, inId)
}
inbounds := make([]*model.Inbound, 0, len(inboundIds))
for i := 0; i < len(inboundIds); i += safeBatchSize {
end := i + safeBatchSize
if end > len(inboundIds) {
end = len(inboundIds)
}
batchInbounds := make([]*model.Inbound, 0, end-i)
err := tx.Model(model.Inbound{}).Where("id IN ?", inboundIds[i:end]).Find(&batchInbounds).Error
if err != nil {
return err
}
inbounds = append(inbounds, batchInbounds...)
}
for inbound_index := range inbounds {
settings := map[string]interface{}{}
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
clients, ok := settings["clients"].([]interface{})
inbEmails := inboundExpiryTimeMap[inbounds[inbound_index].Id]
if ok {
var newClients []interface{}
for client_index := range clients {
c := clients[client_index].(map[string]interface{})
for index := range inbEmails {
if c["email"] == inbEmails[index].Email {
c["expiryTime"] = inbEmails[index].NewExpiryTime
break
}
}
newClients = append(newClients, interface{}(c))
}
settings["clients"] = newClients
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
inbounds[inbound_index].Settings = string(modifiedSettings)
}
}
if len(inboundIds) > 0 {
var inbounds []*model.Inbound
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
if err != nil {
return nil, err
for i := 0; i < len(inbounds); i += safeBatchSize {
end := i + safeBatchSize
if end > len(inbounds) {
end = len(inbounds)
}
for inbound_index := range inbounds {
settings := map[string]interface{}{}
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
clients, ok := settings["clients"].([]interface{})
if ok {
var newClients []interface{}
for client_index := range clients {
c := clients[client_index].(map[string]interface{})
for traffic_index := range dbClientTraffics {
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break
}
}
newClients = append(newClients, interface{}(c))
}
settings["clients"] = newClients
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return nil, err
}
inbounds[inbound_index].Settings = string(modifiedSettings)
}
}
err = tx.Save(inbounds).Error
err := tx.Save(inbounds[i:end]).Error
if err != nil {
logger.Warning("AddClientTraffic update inbounds ", err)
logger.Error(inbounds)
break
}
}
return dbClientTraffics, nil
return nil
}
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
@@ -1030,8 +1107,12 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
if err1 == nil {
logger.Debug("Client disabled by api:", result.Email)
} else {
logger.Debug("Error in disabling client by api:", err1)
needRestart = true
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
logger.Debug("User is already disabled. Nothing to do more...")
} else {
logger.Debug("Error in disabling client by api:", err1)
needRestart = true
}
}
}
s.xrayApi.Close()
@@ -1062,7 +1143,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
clientTraffic.Email = client.Email
clientTraffic.Total = client.TotalGB
clientTraffic.ExpiryTime = client.ExpiryTime
clientTraffic.Enable = true
clientTraffic.Enable = client.Enable
clientTraffic.Up = 0
clientTraffic.Down = 0
clientTraffic.Reset = client.Reset
@@ -1075,7 +1156,7 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
result := tx.Model(xray.ClientTraffic{}).
Where("email = ?", email).
Updates(map[string]interface{}{
"enable": true,
"enable": client.Enable,
"email": client.Email,
"total": client.TotalGB,
"expiry_time": client.ExpiryTime,
@@ -1107,7 +1188,7 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
return false, err
}
for _, client := range clients {
if client.Email == clientEmail {
if client.Email == clientEmail && client.Enable {
s.xrayApi.Init(p.GetAPIPort())
cipher := ""
if string(inbound.Protocol) == "shadowsocks" {
@@ -1292,6 +1373,25 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
return nil, nil
}
func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) {
db := database.GetDB()
var traffics []xray.ClientTraffic
err := db.Model(xray.ClientTraffic{}).Where(`email IN(
SELECT JSON_EXTRACT(client.value, '$.email') as email
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
WHERE
JSON_EXTRACT(client.value, '$.id') in (?)
)`, id).Find(&traffics).Error
if err != nil {
logger.Debug(err)
return nil, err
}
return traffics, err
}
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
db := database.GetDB()
inbound := &model.Inbound{}
@@ -1359,6 +1459,9 @@ func (s *InboundService) MigrationRequirements() {
defer func() {
if err == nil {
tx.Commit()
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
logger.Warningf("VACUUM failed: %v", dbErr)
}
} else {
tx.Rollback()
}
@@ -1468,6 +1571,6 @@ func (s *InboundService) MigrateDB() {
s.MigrationRemoveOrphanedTraffics()
}
func (s *InboundService) GetOnlineClinets() []string {
func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients()
}

View File

@@ -5,7 +5,7 @@ import (
"syscall"
"time"
"x-ui/logger"
"github.com/alireza0/x-ui/logger"
)
type PanelService struct{}

View File

@@ -11,17 +11,18 @@ import (
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"x-ui/config"
"x-ui/database"
"x-ui/logger"
"x-ui/util/common"
"x-ui/util/sys"
"x-ui/xray"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/util/sys"
"github.com/alireza0/x-ui/xray"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
@@ -243,7 +244,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
}
var versions []string
for _, release := range releases {
if release.TagName >= "v1.8.0" {
if release.TagName >= "v26.1.23" {
versions = append(versions, release.TagName)
}
}
@@ -279,6 +280,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
switch osName {
case "darwin":
osName = "macos"
case "windows":
osName = "windows"
}
switch arch {
@@ -286,6 +289,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
arch = "64"
case "arm64":
arch = "arm64-v8a"
case "armv7":
arch = "arm32-v7a"
case "armv6":
arch = "arm32-v6"
case "armv5":
arch = "arm32-v5"
case "386":
arch = "32"
case "s390x":
arch = "s390x"
}
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
@@ -312,19 +325,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
}
func (s *ServerService) UpdateXray(version string) error {
// 1. Stop xray before doing anything
if err := s.StopXrayService(); err != nil {
logger.Warning("failed to stop xray before update:", err)
}
// 2. Download the zip
zipFileName, err := s.downloadXRay(version)
if err != nil {
return err
}
defer os.Remove(zipFileName)
zipFile, err := os.Open(zipFileName)
if err != nil {
return err
}
defer func() {
zipFile.Close()
os.Remove(zipFileName)
}()
defer zipFile.Close()
stat, err := zipFile.Stat()
if err != nil {
@@ -335,19 +352,14 @@ func (s *ServerService) UpdateXray(version string) error {
return err
}
s.xrayService.StopXray()
defer func() {
err := s.xrayService.RestartXray(true)
if err != nil {
logger.Error("start xray failed:", err)
}
}()
// 3. Helper to extract files
copyZipFile := func(zipName string, fileName string) error {
zipFile, err := reader.Open(zipName)
if err != nil {
return err
}
defer zipFile.Close()
os.MkdirAll(filepath.Dir(fileName), 0755)
os.Remove(fileName)
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
if err != nil {
@@ -358,11 +370,23 @@ func (s *ServerService) UpdateXray(version string) error {
return err
}
err = copyZipFile("xray", xray.GetBinaryPath())
// 4. Extract correct binary
if runtime.GOOS == "windows" {
targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
err = copyZipFile("xray.exe", targetBinary)
} else {
err = copyZipFile("xray", xray.GetBinaryPath())
}
if err != nil {
return err
}
// 5. Restart xray
if err := s.xrayService.RestartXray(true); err != nil {
logger.Error("start xray failed:", err)
return err
}
return nil
}
@@ -371,14 +395,39 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
var lines []string
if syslog == "true" {
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
// Run the command
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
// Check if running on Windows - journalctl is not available
if runtime.GOOS == "windows" {
return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
}
// Validate and sanitize count parameter
countInt, err := strconv.Atoi(count)
if err != nil || countInt < 1 || countInt > 10000 {
return []string{"Invalid count parameter - must be a number between 1 and 10000"}
}
// Validate level parameter - only allow valid syslog levels
validLevels := map[string]bool{
"0": true, "emerg": true,
"1": true, "alert": true,
"2": true, "crit": true,
"3": true, "err": true,
"4": true, "warning": true,
"5": true, "notice": true,
"6": true, "info": true,
"7": true, "debug": true,
}
if !validLevels[level] {
return []string{"Invalid level parameter - must be a valid syslog level"}
}
// Use hardcoded command with validated parameters
cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
err = cmd.Run()
if err != nil {
return []string{"Failed to run journalctl command!"}
return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
}
lines = strings.Split(out.String(), "\n")
} else {
@@ -471,14 +520,26 @@ func (s *ServerService) ImportDB(file multipart.File) error {
return common.NewErrorf("Error saving db: %v", err)
}
// Check if we can init db or not
err = database.InitDB(tempPath)
if err != nil {
return common.NewErrorf("Error checking db: %v", err)
// Close temp file before opening via sqlite
if err = tempFile.Close(); err != nil {
return common.NewErrorf("Error closing temporary db file: %v", err)
}
tempFile = nil
// Validate integrity (no migrations / side effects)
if err = database.ValidateSQLiteDB(tempPath); err != nil {
return common.NewErrorf("Invalid or corrupt db file: %v", err)
}
// Stop Xray
s.StopXrayService()
// Stop Xray (ignore error but log)
if errStop := s.StopXrayService(); errStop != nil {
logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
}
// Close existing DB to release file locks (especially on Windows)
if errClose := database.CloseDB(); errClose != nil {
logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
}
// Backup the current database for fallback
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
@@ -554,3 +615,93 @@ func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
return keyPair, nil
}
func (s *ServerService) GetNewmldsa65() (any, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
SeedLine := strings.Split(lines[0], ":")
VerifyLine := strings.Split(lines[1], ":")
seed := strings.TrimSpace(SeedLine[1])
verify := strings.TrimSpace(VerifyLine[1])
keyPair := map[string]any{
"seed": seed,
"verify": verify,
}
return keyPair, nil
}
func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
if len(lines) < 4 {
return nil, common.NewError("invalid ech cert")
}
configList := lines[1]
serverKeys := lines[3]
return map[string]interface{}{
"echServerKeys": serverKeys,
"echConfigList": configList,
}, nil
}
func (s *ServerService) GetNewVlessEnc() (any, error) {
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, err
}
lines := strings.Split(out.String(), "\n")
var auths []map[string]string
var current map[string]string
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Authentication:") {
if current != nil {
auths = append(auths, current)
}
current = map[string]string{
"label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
}
} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 && current != nil {
key := strings.Trim(parts[0], `" `)
val := strings.Trim(parts[1], `" `)
current[key] = val
}
}
}
if current != nil {
auths = append(auths, current)
}
return map[string]any{
"auths": auths,
}, nil
}

View File

@@ -10,13 +10,13 @@ import (
"strings"
"time"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/util/random"
"x-ui/util/reflect_util"
"x-ui/web/entity"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/util/random"
"github.com/alireza0/x-ui/util/reflect_util"
"github.com/alireza0/x-ui/web/entity"
)
//go:embed config.json
@@ -59,6 +59,7 @@ var defaultValueMap = map[string]string{
"subJsonPath": "/json/",
"subJsonURI": "",
"subJsonFragment": "",
"subJsonNoises": "",
"subJsonMux": "",
"subJsonRules": "",
"warp": "",
@@ -282,10 +283,18 @@ func (s *SettingService) SetPort(port int) error {
return s.setInt("webPort", port)
}
func (s *SettingService) SetCertFile(webCertFile string) error {
return s.setString("webCertFile", webCertFile)
}
func (s *SettingService) GetCertFile() (string, error) {
return s.getString("webCertFile")
}
func (s *SettingService) SetKeyFile(webKeyFile string) error {
return s.setString("webKeyFile", webKeyFile)
}
func (s *SettingService) GetKeyFile() (string, error) {
return s.getString("webKeyFile")
}
@@ -317,6 +326,16 @@ func (s *SettingService) GetSecret() ([]byte, error) {
return []byte(secret), err
}
func (s *SettingService) SetBasePath(basePath string) error {
if !strings.HasPrefix(basePath, "/") {
basePath = "/" + basePath
}
if !strings.HasSuffix(basePath, "/") {
basePath += "/"
}
return s.setString("webBasePath", basePath)
}
func (s *SettingService) GetBasePath() (string, error) {
basePath, err := s.getString("webBasePath")
if err != nil {
@@ -340,7 +359,12 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
if err != nil {
defaultLocation := defaultValueMap["timeLocation"]
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
return time.LoadLocation(defaultLocation)
location, err = time.LoadLocation(defaultLocation)
if err != nil {
logger.Errorf("failed to load default location, using UTC: %v", err)
return time.UTC, nil
}
return location, nil
}
return location, nil
}
@@ -405,6 +429,10 @@ func (s *SettingService) GetSubJsonFragment() (string, error) {
return s.getString("subJsonFragment")
}
func (s *SettingService) GetSubJsonNoises() (string, error) {
return s.getString("subJsonNoises")
}
func (s *SettingService) GetSubJsonMux() (string, error) {
return s.getString("subJsonMux")
}

View File

@@ -9,13 +9,13 @@ import (
"strings"
"time"
"x-ui/config"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/web/locale"
"x-ui/xray"
"github.com/alireza0/x-ui/config"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/web/locale"
"github.com/alireza0/x-ui/xray"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
@@ -135,7 +135,7 @@ func (t *Tgbot) OnReceive() {
isAdmin := checkAdmin(tgId)
if update.Message == nil {
if update.CallbackQuery != nil {
t.asnwerCallback(update.CallbackQuery)
t.answerCallback(update.CallbackQuery)
}
} else {
if update.Message.IsCommand() {
@@ -196,7 +196,7 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
t.SendAnswer(chatId, msg, isAdmin)
}
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery) {
func (t *Tgbot) answerCallback(callbackQuery *tgbotapi.CallbackQuery) {
// Respond to the callback query, telling Telegram to show the user
// a message with the data received.
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
@@ -424,14 +424,14 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
func (t *Tgbot) getInboundUsages() string {
info := ""
// get traffic
inbouds, err := t.inboundService.GetAllInbounds()
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
logger.Warning("GetAllInbounds run failed:", err)
info += t.I18nBot("tgbot.answers.getInboundsFailed")
} else {
// NOTE:If there no any sessions here,need to notify here
// TODO:Sub-node push, automatic conversion format
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
@@ -539,7 +539,7 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
}
func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbouds, err := t.inboundService.SearchInbounds(remark)
inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil {
logger.Warning(err)
msg := t.I18nBot("tgbot.wentWrong")
@@ -547,13 +547,13 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
return
}
if len(inbouds) == 0 {
if len(inbounds) == 0 {
msg := t.I18nBot("tgbot.noInbounds")
t.SendMsgToTgbot(chatId, msg)
return
}
for _, inbound := range inbouds {
for _, inbound := range inbounds {
info := ""
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))

View File

@@ -3,9 +3,9 @@ package service
import (
"errors"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"github.com/alireza0/x-ui/database"
"github.com/alireza0/x-ui/database/model"
"github.com/alireza0/x-ui/logger"
"gorm.io/gorm"
)

171
web/service/warp.go Normal file
View File

@@ -0,0 +1,171 @@
package service
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/util/common"
)
type WarpService struct {
SettingService
}
func (s *WarpService) GetWarpData() (string, error) {
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
return warp, nil
}
func (s *WarpService) DelWarpData() error {
err := s.SettingService.SetWarp("")
if err != nil {
return err
}
return nil
}
func (s *WarpService) GetWarpConfig() (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := &bytes.Buffer{}
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error) {
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Add("CF-Client-Version", "a-7.21-0721")
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := &bytes.Buffer{}
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
var rspData map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &rspData)
if err != nil {
return "", err
}
deviceId := rspData["id"].(string)
token := rspData["token"].(string)
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
if !ok {
logger.Debug("Error accessing license value.")
return "", err
}
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
s.SettingService.SetWarp(warpData)
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
return result, nil
}
func (s *WarpService) SetWarpLicense(license string) (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
data := fmt.Sprintf(`{"license": "%s"}`, license)
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := &bytes.Buffer{}
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
var response map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &response)
if err != nil {
return "", err
}
if response["success"] == false {
errorArr, _ := response["errors"].([]interface{})
errorObj := errorArr[0].(map[string]interface{})
return "", common.NewError(errorObj["code"], errorObj["message"])
}
warpData["license_key"] = license
newWarpData, err := json.MarshalIndent(warpData, "", " ")
if err != nil {
return "", err
}
s.SettingService.SetWarp(string(newWarpData))
return string(newWarpData), nil
}

View File

@@ -3,10 +3,11 @@ package service
import (
"encoding/json"
"errors"
"runtime"
"sync"
"x-ui/logger"
"x-ui/xray"
"github.com/alireza0/x-ui/logger"
"github.com/alireza0/x-ui/xray"
"go.uber.org/atomic"
)
@@ -32,7 +33,19 @@ func (s *XrayService) GetXrayErr() error {
if p == nil {
return nil
}
return p.GetErr()
err := p.GetErr()
if err == nil {
return nil
}
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return nil
}
return err
}
func (s *XrayService) GetXrayResult() string {
@@ -45,7 +58,15 @@ func (s *XrayService) GetXrayResult() string {
if p == nil {
return ""
}
result = p.GetResult()
if runtime.GOOS == "windows" && result == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return ""
}
return result
}

View File

@@ -1,16 +1,11 @@
package service
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"x-ui/util/common"
"x-ui/xray"
"github.com/alireza0/x-ui/util/common"
"github.com/alireza0/x-ui/xray"
)
type XraySettingService struct {
@@ -32,142 +27,3 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
}
return nil
}
func (s *XraySettingService) GetWarpData() (string, error) {
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
return warp, nil
}
func (s *XraySettingService) GetWarpConfig() (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string, error) {
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Add("CF-Client-Version", "a-7.21-0721")
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
var rspData map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &rspData)
if err != nil {
return "", err
}
deviceId := rspData["id"].(string)
token := rspData["token"].(string)
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
if !ok {
fmt.Println("Error accessing license value.")
return "", err
}
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
s.SettingService.SetWarp(warpData)
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
return result, nil
}
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
data := fmt.Sprintf(`{"license": "%s"}`, license)
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
warpData["license_key"] = license
newWarpData, err := json.MarshalIndent(warpData, "", " ")
if err != nil {
return "", err
}
s.SettingService.SetWarp(string(newWarpData))
println(string(newWarpData))
return string(newWarpData), nil
}

View File

@@ -3,9 +3,9 @@ package session
import (
"encoding/gob"
"x-ui/database/model"
"github.com/alireza0/x-ui/database/model"
sessions "github.com/Calidity/gin-sessions"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
@@ -23,15 +23,6 @@ func SetLoginUser(c *gin.Context, user *model.User) error {
return s.Save()
}
func SetMaxAge(c *gin.Context, maxAge int) error {
s := sessions.Default(c)
s.Options(sessions.Options{
Path: "/",
MaxAge: maxAge,
})
return s.Save()
}
func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c)
obj := s.Get(loginUser)
@@ -49,8 +40,12 @@ func IsLogin(c *gin.Context) bool {
func ClearSession(c *gin.Context) {
s := sessions.Default(c)
s.Clear()
basePath := c.GetString("base_path")
if basePath == "" {
basePath = "/"
}
s.Options(sessions.Options{
Path: "/",
Path: basePath,
MaxAge: -1,
})
s.Save()

View File

@@ -44,7 +44,7 @@
"monitor" = "Listen IP"
"certificate" = "Certificate"
"fail" = " Failed"
"success" = " Successful"
"success" = " Successfully"
"getVersion" = "Get Version"
"install" = "Install"
"clients" = "Clients"
@@ -123,6 +123,7 @@
"remark" = "Remark"
"protocol" = "Protocol"
"port" = "Port"
"portMap" = "Port Mapping"
"traffic" = "Traffic"
"details" = "Details"
"transportConfig" = "Transport Config"
@@ -221,9 +222,6 @@
"requestHeader" = "Request Header"
"responseHeader" = "Response Header"
[pages.inbounds.stream.quic]
"encryption" = "Encryption"
[pages.settings]
"title" = "Panel Settings"
"save" = "Save"
@@ -301,6 +299,14 @@
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
"fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
"fragmentSett" = "Fragmentation Settings"
"noisesDesc" = "Enable Noises."
"noisesSett" = "Noises Settings"
"mux" = "Mux"
"muxDesc" = "Transmit multiple independent data streams within an established data stream."
"muxSett" = "Mux Settings"
"direct" = "Direct Connection"
"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country."
[pages.settings.toasts]
"modifySettings" = "Modify Settings"
@@ -321,14 +327,17 @@
"logConfigsDesc" = "Enabling logs may affect your server's efficiency. It is recommended to enable it wisely only when necessary."
"blockConfigs" = "Protection Shield"
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
"blockCountryConfigs" = "Block Country"
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
"directCountryConfigs" = "Direct Country"
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
"ipv4Configs" = "IPv4 Routing"
"ipv4ConfigsDesc" = "These options will route traffic based on specific requested destination via server's IPv4."
"warpConfigs" = "WARP Config"
"warpConfigsDesc" = "These options will route traffic based on specific requested destination via WARP."
"basicRouting" = "Basic Routing"
"blockConnectionsConfigsDesc" = "These options will block traffic based on the specific requested country."
"directConnectionsConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server."
"blockips" = "Block IPs"
"blockdomains" = "Block Domains"
"directips" = "Direct IPs"
"directdomains" = "Direct Domains"
"ipv4Routing" = "IPv4 Routing"
"ipv4RoutingDesc" = "These options will route traffic based on specific requested destination via server's IPv4."
"warpRouting" = "WARP Routing"
"warpRoutingDesc" = "These options will route traffic based on specific requested destination via WARP."
"Template" = "Advanced Xray Configuration Template"
"TemplateDesc" = "The final Xray config file will be generated based on this template."
"FreedomStrategy" = "Freedom Protocol Strategy"
@@ -337,50 +346,8 @@
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
"Torrent" = "Block BitTorrent Protocol"
"TorrentDesc" = "Blocks BitTorrent protocol."
"PrivateIp" = "Block Connection to Private IPs"
"PrivateIpDesc" = "Blocks establishing connections to private IP ranges."
"Ads" = "Block Ads"
"AdsDesc" = "Blocks advertising websites."
"Family" = "Family Protection"
"FamilyDesc" = "Blocks adult content, and malware websites."
"IRIp" = "Block Connection to Iran IPs"
"IRIpDesc" = "Blocks establishing connections to Iran IP ranges."
"IRDomain" = "Block Connection to Iran Domains"
"IRDomainDesc" = "Blocks establishing connections to Iran domains."
"ChinaIp" = "Block Connection to China IPs"
"ChinaIpDesc" = "Blocks establishing connections to China IP ranges."
"ChinaDomain" = "Block Connection to China Domains"
"ChinaDomainDesc" = "Blocks establishing connections to China domains."
"RussiaIp" = "Block Connection to Russia IPs"
"RussiaIpDesc" = "Blocks establishing connections to Russia IP ranges."
"RussiaDomain" = "Block Connection to Russia Domains"
"RussiaDomainDesc" = "Blocks establishing connections to Russia domains."
"DirectIRIp" = "Direct Connection to Iran IPs"
"DirectIRIpDesc" = "Directly establishes connections to Iran IP ranges."
"DirectIRDomain" = "Direct Connection to Iran Domains"
"DirectIRDomainDesc" = "Directly establishes connections to Iran domains."
"DirectChinaIp" = "Direct Connection to China IPs"
"DirectChinaIpDesc" = "Directly establishes connections to China IP ranges."
"DirectChinaDomain" = "Direct Connection to China Domains"
"DirectChinaDomainDesc" = "Directly establishes connections to China domains."
"DirectRussiaIp" = "Direct Connection to Russia IPs"
"DirectRussiaIpDesc" = "Directly establishes connections to Russia IP ranges."
"DirectRussiaDomain" = "Direct Connection to Russia Domains"
"DirectRussiaDomainDesc" = "Directly establishes connections to Russia domains."
"GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "Routes traffic to Google via IPv4."
"NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "Routes traffic to Netflix via IPv4."
"GoogleWARP" = "Google"
"GoogleWARPDesc" = "Routes traffic to Google via WARP."
"OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
"NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
"MetaWARP" = "Meta"
"MetaWARPDesc" = "Routes traffic to Meta (Instagram, Facebook, WhatsApp, Threads,...) via WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
"completeTemplate" = "All"
"Inbounds" = "Inbounds"
"Outbounds" = "Outbounds"
@@ -393,6 +360,10 @@
"accessLogDesc" = "The file path for the access log."
"errorLog" = "Error Log"
"errorLogDesc" = "The file path for the error log."
"dnsLog" = "DNS Log"
"dnsLogDesc" = "Whether to enable DNS query logs"
"maskAddress" = "Mask Address"
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
[pages.xray.rules]
"first" = "First"
@@ -433,6 +404,7 @@
"editBalancer" = "Edit Balancer"
"balancerStrategy" = "Strategy"
"balancerSelectors" = "Selectors"
"fallback" = "Fallback"
"tag" = "Tag"
"tagDesc" = "Unique Tag"
"balancerDesc" = "It is not possible to use both balancerTag and outboundTag simultaneously. If both are used together, only outboundTag will function."
@@ -445,6 +417,12 @@
"psk" = "PreShared Key"
"domainStrategy" = "Domain Strategy"
[pages.xray.tun]
"nameDesc" = "The name of the TUN interface. Default is 'xrayN', where N is some number"
"mtuDesc" = "Maximum Transmission Unit. The maximum size of data packets. Default is 1500"
"userLevel" = "User Level"
"userLevelDesc" = "All connections made through this inbound will use this user level. Default is 0"
[pages.xray.dns]
"enable" = "Enable DNS"
"enableDesc" = "Enables built-in DNS server."
@@ -455,6 +433,7 @@
"add" = "Add Server"
"edit" = "Edit Server"
"domains" = "Domains"
"expectIPs" = "Expect IPs"
[pages.xray.fakedns]
"add" = "Add Fake DNS"

Some files were not shown because too many files have changed in this diff Show More