Compare commits

..

137 Commits

Author SHA1 Message Date
Shishkevich D.
a811225610 fix: protocol checking during random uuidv4 generation
fixes the https://github.com/MHSanaei/3x-ui/issues/2750 issue
2025-03-10 22:09:51 +07:00
mhsanaei
1893c3814d v2.5.5 2025-03-10 14:33:58 +01:00
mhsanaei
422c391f96 Xray log: show failed on error log level 2025-03-10 14:31:06 +01:00
mhsanaei
f7f95ffbae bug fix - xray log 2025-03-10 13:46:46 +01:00
mhsanaei
f408bd7c77 Xray core v2.3.6 + update dependencies 2025-03-10 09:51:31 +01:00
Shishkevich D.
ad13ce6cde fix: generating shortIds for vless reality (#2745) 2025-03-09 19:37:53 +07:00
Shishkevich D.
c35179d924 chore: remove unused variable 2025-03-09 06:38:45 +00:00
Shishkevich D.
cedc7f0fb8 chore: refactoring RandomUtil class
now we use window.crypto.getRandomValues to generate random values.
2025-03-09 06:37:05 +00:00
Shishkevich D.
64fa0e97a3 chore: use crypto.randomUUID() for generating UUIDv4 2025-03-09 06:09:42 +00:00
Shishkevich D.
a45e9de472 chore: use Base64 library for generating SS password 2025-03-09 06:06:27 +00:00
Shishkevich D.
101e9ebf35 fix: modals style 2025-03-09 06:01:27 +00:00
Shishkevich D.
17a76d2843 Revert "chore: add missing params for grpc stream settings (outbound)"
This reverts commit 1c59afe031.
2025-03-09 05:38:34 +00:00
Shishkevich D.
a23a5de540 Revert "chore: add new grpc params for outbound (#2744)"
This reverts commit c49ec9a74c.
2025-03-09 05:37:50 +00:00
Shishkevich D.
a16e83468b chore: add dns type for kcp protocol
see https://xtls.github.io/config/transports/mkcp.html#headerobject
2025-03-09 12:23:35 +07:00
Shishkevich D.
1c59afe031 chore: add missing params for grpc stream settings (outbound) 2025-03-09 04:49:17 +00:00
Shishkevich D.
c49ec9a74c chore: add new grpc params for outbound (#2744) 2025-03-09 11:28:12 +07:00
mhsanaei
a0dd101d97 tgbot - restart
change restart force to restart
2025-03-08 23:08:04 +01:00
mhsanaei
700cf9c10b minor changes 2025-03-08 18:14:48 +01:00
Shishkevich D.
697cd5e6d9 Code refactoring (#2739)
* refactor: switching to the use of typed props

* refactor: `password-input` -> `a-password-input`

* fix: qr modal copy error
2025-03-08 22:41:27 +07:00
Shishkevich D.
c6d27a4463 chore: pretty backup and xray version modal (#2737)
* chore: pretty `backup & restore` modal

* chore: pretty `xray version` modal

* fix: new `xray version` modal style
2025-03-08 20:41:23 +07:00
Shishkevich D.
751f564c4a fix: base64 encoding on vmess/shadowsocks inbounds (#2736) 2025-03-08 12:33:34 +07:00
Shishkevich D.
6658f648e6 refactor: move language manager to utils (#2735) 2025-03-08 09:54:41 +07:00
Shishkevich D.
d6f9f3f6d3 chore: add empty screens for empty data (balancers, reverses, dns) 2025-03-07 22:17:14 +07:00
Shishkevich D.
fad6c497eb chore: add empty screens for empty data (balancers, reverses, dns)
cleaned up some of the margins & paddings
2025-03-07 13:56:03 +00:00
Sanaei
42fa64770b Merge pull request #2732 from shishkevichd/refactor/refactor-1
Code refactoring
2025-03-07 12:40:35 +01:00
Shishkevich D.
0a207b8a2c refactor: merging all util functions into classes 2025-03-07 09:07:23 +00:00
Shishkevich D.
26bf693dbd refactor: move copy function to utils.js 2025-03-07 07:27:33 +00:00
Shishkevich D.
7483fb2ec5 refactor: delete base64js
instead of base64 library you can use built-in JS functions `btoa()` and `atob()`
2025-03-07 07:11:03 +00:00
Shishkevich D.
2d8cca3a2e refactor: delete clipboardjs (#2727)
text copying can be done without using additional libraries
2025-03-06 20:43:46 +01:00
UltraMegaPotato
cf7fec1351 Change the cpu usage interval from second to minute (#2729)
feature(check_cpu_usage): Change the usage interval from second to minute
2025-03-06 20:38:58 +01:00
mhsanaei
361849b9db Balancer fallbackTag #2724 2025-03-06 20:35:17 +01:00
Shishkevich D.
c13db7922e Pretty Panel and Xray settings (#2726)
* chore: refactor `setting-list-item` component

* chore: remove padding

* chore: replace settings list with settings collapse panels

* chore: add missing translations

* chore: fix translation
2025-03-06 11:17:25 +01:00
mhsanaei
d6d05a9b4d v2.5.4 2025-03-05 15:42:51 +01:00
mhsanaei
3caace2cb6 update dependencies 2025-03-05 15:40:27 +01:00
Shishkevich D.
99f26be30d feat: add statistics section (#2718)
the "Outbounds Traffic" parameter, which was misleading, was also renamed and moved
2025-03-05 13:27:25 +01:00
mhsanaei
b0edd24c52 Noise: Add hex 2025-03-05 13:22:07 +01:00
mhsanaei
f0cfd48f66 Sockopt: Add addressPortStrategy 2025-03-05 13:14:17 +01:00
Shishkevich D.
f5aea03765 chore: add global params for DNS (#2713)
parameter list: `disableCache`, `disableFallback`, `disableFallbackIfMatch`, `clientIp`
2025-03-04 14:18:51 +01:00
mhsanaei
14cdde371f better view for uTLS 2025-03-04 12:50:49 +01:00
mhsanaei
99a23f25d5 TLS-REALITY : hide value of private key 2025-03-04 10:36:02 +01:00
Shishkevich D.
653ec90451 chore: pretty dns section in xray settings (#2700) 2025-03-04 09:55:13 +01:00
Shishkevich D.
91a84db479 chore: pretty auth tab in panel settings (#2701) 2025-03-04 09:54:59 +01:00
mhsanaei
0f97eca314 Xray core v25.3.3 2025-03-04 09:54:10 +01:00
mhsanaei
fb79081aa1 TLS fingerprints: randomizednoalpn 2025-03-03 10:20:52 +01:00
mhsanaei
ea19fb8ff6 fix mistake 2025-03-01 10:19:52 +01:00
atarwn
f4cfe9eb63 Improved ru_RU translation (#2677)
* Improved ru_RU translation

Some words have been changed to sound nicer. I made some words less complicated to understand. I also fixed some warnings and messages so they are easier to read.

* Apply suggestions from code review
2025-02-28 10:42:27 +01:00
mhsanaei
4def70a006 Xray core buggy version removed 2025-02-27 13:40:56 +01:00
mhsanaei
e0e9e2681a docker: go 1.24 2025-02-25 18:56:35 +01:00
mhsanaei
31e1581d6b v2.5.3 2025-02-25 18:43:29 +01:00
mhsanaei
018e98a510 Go v1.24.0 2025-02-25 18:43:15 +01:00
mhsanaei
21ea673c30 Make wget verify certificates part2 #2661
Co-Authored-By: İrem Kuyucu <siren@kernal.eu>
2025-02-24 13:15:18 +01:00
Shishkevich D.
08b55da408 feat: add quic protocol in xray rule modal (#2666) 2025-02-24 09:23:59 +01:00
atarwn
7a3ee69a7f Virtuozzo linux support (#2668) 2025-02-24 09:22:34 +01:00
mhsanaei
664bd9b596 bug fix #2660 2025-02-22 14:31:08 +01:00
mhsanaei
ceb1217121 serverNameToVerify to verifyPeerCertInNames #2662 2025-02-22 14:09:52 +01:00
mhsanaei
e754523689 Xray core v25.2.21 2025-02-22 13:46:15 +01:00
İrem Kuyucu
e84503feec Make wget verify certificates (#2661) 2025-02-22 11:53:36 +01:00
AAA
1bbf31df9f feat(externalTrafficJob): External Traffic Inform (#2660)
* Add Setting entity + GUI field in panel settings

* Add a missing 'Traffic' in InformEnabale field

* Add ExternalTrafficURL Post request call

* Add translation + cleanup

* Move options to General tab

---------

Co-authored-by: root <root@vm3562019.stark-industries.solutions>
Co-authored-by: root <root@vm3688062.stark-industries.solutions>
2025-02-22 10:45:14 +01:00
mhsanaei
49bfff9fa5 v2.5.2 2025-02-04 11:38:57 +01:00
Sanaei
d18a1a37ce revert group management (#2656)
* Revert "json post base path bug fixed (#2647)"

This reverts commit 04cf250a54.

* Revert "Group Management of Subscription Clients"

* Revert "fix getSubGroupClients for enable/disable and edit clients."

* Revert "Enhance database initialization in db.go (#2645)"

This reverts commit 66fe84181b.

* Revert "Add checkpoint handling in CloseDB function (#2646)"

This reverts commit 4dd40f6f19.

* Revert "Improved database model migration and added indexing (#2655)"

This reverts commit b922d986d6.
2025-02-04 11:27:58 +01:00
mhsanaei
04c6b2722b README: Persian 2025-02-03 20:30:42 +01:00
mhsanaei
94d651fc93 v2.5.1 2025-02-03 17:40:33 +01:00
mhsanaei
aae0cb37b7 Xray Core v25.1.30 2025-02-03 17:37:01 +01:00
Zahar Izmailov
b922d986d6 Improved database model migration and added indexing (#2655) 2025-02-03 13:36:03 +01:00
Abolfazl Fazilat
8a7cffd63f Completed translation for missing text (#2653) 2025-01-31 18:54:52 +01:00
Sanaei
c8e8c97afc Merge pull request #2652 from Incognito-Coder/main
Some Improvement
2025-01-31 16:01:14 +01:00
AghayeCoder
46ba4c4518 fix getSubGroupClients for enable/disable and edit clients. 2025-01-31 17:27:09 +03:30
AghayeCoder
a787ab497c switcher for outbound traffic 2025-01-31 17:24:03 +03:30
mhsanaei
3be204f272 v2.5.0 2025-01-28 00:09:20 +01:00
mhsanaei
de13729a97 update dependencies 2025-01-28 00:09:12 +01:00
mhsanaei
468eb8b908 Update release.yml
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:13:28 +01:00
mhsanaei
e95a748e77 docker
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:13:12 +01:00
mhsanaei
34e2d961f5 quiet build
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:09:39 +01:00
mhsanaei
b4a1d81444 [subJson] better direct options
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-26 19:43:02 +01:00
mhsanaei
46ef506aa6 TLS - serverNameToVerify 2025-01-26 19:33:50 +01:00
mhsanaei
51220917c4 outbound Traffic - default false
if you need it you need to change it manually to true
2025-01-26 11:22:42 +01:00
mhsanaei
b34956647b minor change 2025-01-26 11:07:45 +01:00
mhsanaei
5c4e2dfd39 default setting - scStreamUpServerSecs 2025-01-26 11:07:35 +01:00
Ivan Zorin
dd4c2adb37 Fix non-MultiUser dbInbounds (#2649) 2025-01-26 11:03:06 +01:00
mhsanaei
2dec7f48f5 bug fix - get client ips 2025-01-24 17:16:16 +01:00
Ali Rahimi
04cf250a54 json post base path bug fixed (#2647)
* json post base path bug fixed

* added comment field to group client management
2025-01-23 21:33:47 +01:00
mhsanaei
ac9ab828b5 GO v1.23.5 + update dependencies 2025-01-21 23:04:03 +01:00
Zahar Izmailov
4dd40f6f19 Add checkpoint handling in CloseDB function (#2646)
* Add checkpoint handling in CloseDB function

---------

Co-authored-by: Zakhar Izmaylov <ptdev@kedruss.ru>
2025-01-21 22:55:21 +01:00
mhsanaei
7911eeb69f XHTTP - scStreamUpServerSecs 2025-01-21 22:43:13 +01:00
Ali Rahimi
6e9180a665 Group Management of Subscription Clients (#2644)
* add group user with the same subscription id to all inbounds

* code format compare

* add await for reset client traffic

* en language changed

* added client traffic syncer job

* handle exist email duplicate in sub group

* multi reset and delete request for clients group

* add client traffic syncer setting option

* vi translate file updated

* auto open qr-modal bug fixed
2025-01-21 03:01:54 +01:00
Zahar Izmailov
66fe84181b Enhance database initialization in db.go (#2645)
- Updated GORM configuration to skip default transactions and prepare statements.
- Modified the database connection string to include caching and journal mode settings.
- Executed several PRAGMA statements to optimize SQLite performance and enable foreign key support.

These changes improve database handling and performance in the application.

Co-authored-by: Zakhar Izmaylov <ptdev@kedruss.ru>
2025-01-21 02:59:30 +01:00
Tara Rostami
7b7eb98acb Minor Fixes (CSS) (#2641) 2025-01-15 12:50:50 +01:00
Alex Churin
7eb5afdd8d wireguard modal fix (#2640) 2025-01-14 16:08:19 +01:00
xtclovver
522ccda71c Update translate.ru_RU.toml (#2637)
Fixed ru translation
2025-01-13 18:23:19 +01:00
mortefy
f780efb430 Fixed ru translation (#2638) 2025-01-13 18:22:35 +01:00
mhsanaei
783f1a073e time Location - local 2025-01-11 15:13:49 +01:00
Dmitiry Vinogradov
a4c38ec8ae fail2ban service in docker container (#2632)
docker container

Co-authored-by: Dmitrij Vinogradov <dmitrij.vinogradov@gmail.com>
2025-01-11 13:41:48 +01:00
Igor Semenov
0c47771671 Add Russian domains (#2635)
Add Russian domains
2025-01-11 13:39:32 +01:00
Tara Rostami
67920a1962 Minor Fixes (UI) (#2636)
* Minor Fixes (UI)

* Update custom.min.css
2025-01-11 13:38:26 +01:00
mhsanaei
49d3957c07 bug fix 2025-01-05 21:04:18 +01:00
mhsanaei
ee946ceab2 iplimit: ipRegex improved
When the client has MUX enabled, a TCP or UDP prefix appears before the IP address. We initially weren’t aware of this behavior, but we have now resolved the issue.
2025-01-05 18:58:51 +01:00
LoST
b650064177 Firewall improvements (#2630)
* The menu has been completed

*Added firewall shutdown

*Improved port removal process (optional)
2025-01-05 16:01:56 +01:00
mhsanaei
9fb9d7201e Add custom v2ray rules for Russia
runetfreedom/russia-v2ray-rules-dat
2025-01-05 15:39:40 +01:00
mhsanaei
4a3b9b913d bug fix - reality settings 2025-01-01 22:49:29 +01:00
mhsanaei
da674d44cf httpupgrade: remove host from header 2025-01-01 20:18:28 +01:00
LoST
cc3252531b Added "comment" in all languages (#2631)
- Added a "comment" in all languages

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-01-01 19:13:55 +01:00
mhsanaei
284731deeb xmux - hMaxReusableSecs 2025-01-01 18:48:47 +01:00
mhsanaei
9bc5c1d070 tcpNoDelay to penetrate 2025-01-01 18:42:50 +01:00
mhsanaei
26a7700557 Xray core buggy version removed
v1.8.24 and
major >=25.1.1
2025-01-01 18:36:46 +01:00
mhsanaei
b6a919218a Xray Core v25.1.1 2025-01-01 18:30:36 +01:00
mhsanaei
6cc07254e0 Fallback - Reality
this is not my problem they don't mention it on their document
2024-12-30 00:38:49 +01:00
mhsanaei
4ad5a5aba4 update dependencies 2024-12-28 15:15:01 +01:00
mhsanaei
7ab8164de4 Xray Core v24.12.28 2024-12-28 15:14:39 +01:00
MHSanaei
195effd177 v2.4.11 2024-12-27 21:49:45 +01:00
MHSanaei
747ad3b9c8 bug fix - outbound xhttp link 2024-12-27 21:30:51 +01:00
mhsanaei
cf879f9527 bug fix - vmess tls 2024-12-27 17:59:59 +01:00
mhsanaei
04c658f1a0 Client: Comment
now you can add Comment and write anything you want to remember
2024-12-27 13:48:07 +01:00
mhsanaei
2ab1a174db moment v2.30.1 2024-12-25 20:27:48 +01:00
mhsanaei
dfca2af997 axios v1.7.9 2024-12-25 19:09:54 +01:00
Baton34
0a7d15b48c RU: translation fix (#2620) 2024-12-22 14:10:06 +01:00
mhsanaei
518bc72f90 media - telebot 2024-12-21 14:00:21 +01:00
mhsanaei
3c65209ce9 Stargazers over Time - variant=adaptive 2024-12-21 13:52:05 +01:00
LoST
174535b05d unnecessary sudo removed (#2619)
All the commands do not work exactly, but since we already work in the beginning on behalf of root, then sudo is not necessary at the beginning. Is that right? 

P.S. when selected in the menu, it says that "command not found".
2024-12-20 23:48:10 +01:00
Tara Rostami
fff54fe7f3 fix bug (#2618) 2024-12-20 19:06:19 +01:00
LoST
0859d230b0 Firewall management: improved (#2614)
* fix permissions

* Update install func + add/edit func open/close ports + status firewall

* hotfix

* subport
2024-12-20 18:43:47 +01:00
KiselevAlexander
02998c5467 Added ip limit data and controls to client info modal (#2617) 2024-12-20 18:34:30 +01:00
Tara Rostami
3f38c42852 fail2ban: better ipv6 validation (#2615) 2024-12-20 18:33:27 +01:00
mhsanaei
49295661fd fail2ban - unban fix
sorry for my mistake
2024-12-19 23:24:10 +01:00
mhsanaei
e4301f8d93 fail2ban - allports 2024-12-19 22:49:25 +01:00
mhsanaei
c6586f8df2 fix typo 2024-12-19 14:53:09 +01:00
mhsanaei
8e81008cdc v2.4.10 2024-12-19 10:54:17 +01:00
mhsanaei
e33ad809d6 Xray Core v24.12.18 2024-12-19 10:48:43 +01:00
mhsanaei
d804043a18 fail2ban - bantime 30min 2024-12-18 13:23:32 +01:00
mhsanaei
0fb0df7056 fail2ban - ipv4 & 6 2024-12-18 12:31:05 +01:00
mhsanaei
73e90e0eaa TLS, REALITY : fingerprint set default to chrome 2024-12-17 23:25:54 +01:00
mhsanaei
44ef1ac9a6 remove insecure cipher suites list 2024-12-17 23:18:16 +01:00
mhsanaei
aeac7f2c8b UTLS: unsafe 2024-12-17 23:17:12 +01:00
mhsanaei
39ef172b87 fail2ban - ban and unban an IP Address 2024-12-17 17:59:04 +01:00
mhsanaei
0df85cc3d9 XHTTP: server & client
Remove scMinPostsIntervalMs, xmux, noGRPCHeader from the server side and add them to the client side.

Before you could have them on sub json but I decided to remove them.
2024-12-17 10:39:37 +01:00
MHSanaei
f0f4f082ae improve iplimit 2024-12-16 14:26:47 +01:00
MHSanaei
b29bd993d4 fix session
twice  set-cookie bug fixed
2024-12-16 14:24:59 +01:00
MHSanaei
127eaf69b6 fail2ban - unban all without restart 2024-12-16 13:28:53 +01:00
88 changed files with 3902 additions and 2185 deletions

View File

@@ -7,7 +7,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -31,6 +31,8 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with:
install: true
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3

View File

@@ -72,7 +72,7 @@ jobs:
export GOARCH=s390x export GOARCH=s390x
export CC=s390x-linux-gnu-gcc export CC=s390x-linux-gnu-gcc
fi fi
go build -o xui-release -v main.go go build -ldflags "-w -s" -o xui-release -v main.go
mkdir x-ui mkdir x-ui
cp xui-release x-ui/ cp xui-release x-ui/
@@ -83,43 +83,43 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v24.12.15/" Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.3.6/"
if [ "${{ matrix.platform }}" == "amd64" ]; then 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 unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then 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 unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then 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 unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "armv6" ]; then elif [ "${{ matrix.platform }}" == "armv6" ]; then
wget ${Xray_URL}Xray-linux-arm32-v6.zip wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
unzip Xray-linux-arm32-v6.zip unzip Xray-linux-arm32-v6.zip
rm -f Xray-linux-arm32-v6.zip rm -f Xray-linux-arm32-v6.zip
elif [ "${{ matrix.platform }}" == "386" ]; then 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 unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip rm -f Xray-linux-32.zip
elif [ "${{ matrix.platform }}" == "armv5" ]; then elif [ "${{ matrix.platform }}" == "armv5" ]; then
wget ${Xray_URL}Xray-linux-arm32-v5.zip wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
unzip Xray-linux-arm32-v5.zip unzip Xray-linux-arm32-v5.zip
rm -f Xray-linux-arm32-v5.zip rm -f Xray-linux-arm32-v5.zip
elif [ "${{ matrix.platform }}" == "s390x" ]; then elif [ "${{ matrix.platform }}" == "s390x" ]; then
wget ${Xray_URL}Xray-linux-s390x.zip wget -q ${Xray_URL}Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip rm -f Xray-linux-s390x.zip
fi fi
rm -f geoip.dat geosite.dat rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget -q 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 -q 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 -q -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 -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }} mv xray xray-linux-${{ matrix.platform }}
cd ../.. cd ../..

View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Start fail2ban # Start fail2ban
fail2ban-client -x start [ $X_UI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
# Run x-ui # Run x-ui
exec /app/x-ui exec /app/x-ui

View File

@@ -27,14 +27,14 @@ case $1 in
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v24.12.15/Xray-linux-${ARCH}.zip" wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.3.6/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget -q 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 -q 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 -q -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 -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
cd ../../ cd ../../

View File

@@ -1,7 +1,7 @@
# ======================================================== # ========================================================
# Stage: Builder # Stage: Builder
# ======================================================== # ========================================================
FROM golang:1.23-alpine AS builder FROM golang:1.24-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
@@ -15,7 +15,7 @@ COPY . .
ENV CGO_ENABLED=1 ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" 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 ./DockerInit.sh "$TARGETARCH" RUN ./DockerInit.sh "$TARGETARCH"
# ======================================================== # ========================================================
@@ -48,6 +48,7 @@ RUN chmod +x \
/app/x-ui \ /app/x-ui \
/usr/bin/x-ui /usr/bin/x-ui
ENV X_UI_ENABLE_FAIL2BAN="true"
VOLUME [ "/etc/x-ui" ] VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ] CMD [ "./x-ui" ]
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]

View File

@@ -1,4 +1,4 @@
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) [English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center"> <p align="center">
<picture> <picture>
@@ -569,7 +569,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
<img alt="3x-ui" src="./media/06-configs-light.png"> <img alt="3x-ui" src="./media/06-configs-light.png">
</picture> </picture>
<picture> <picture>
<img alt="3x-ui" src="./media/7.png"> <source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
<img alt="3x-ui" src="./media/07-bot-light.png">
</picture> </picture>
## Un agradecimiento especial a ## Un agradecimiento especial a
@@ -579,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## Reconocimientos ## Reconocimientos
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas de v2ray/xray y v2ray/xray-clients con dominios iraníes integrados y un enfoque en seguridad y bloqueo de anuncios._ - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas de v2ray/xray y v2ray/xray-clients con dominios iraníes integrados y un enfoque en seguridad y bloqueo de anuncios._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _Un dominio alojado en Vietnam y una lista de bloqueo con la máxima eficiencia para vietnamitas._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _Este repositorio contiene reglas de enrutamiento de V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueados en Rusia._
## Estrellas a lo largo del tiempo ## Estrellas a lo largo del tiempo
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) [![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

527
README.fa_IR.md Normal file
View File

@@ -0,0 +1,527 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
**یک پنل وب پیشرفته • ساخته شده بر پایه Xray Core**
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **سلب مسئولیت:** این پروژه صرفاً برای اهداف آموزشی و تحقیقاتی است. استفاده از آن برای مقاصد غیرقانونی یا در محیط‌های عملیاتی ممنوع است.
**اگر این پروژه برای شما مفید بوده، می‌توانید با دادن یک**:star2: از آن حمایت کنید.
<p align="left">
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
<img src="./media/buymeacoffe.png" alt="Image">
</a>
</p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## نصب و ارتقا
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## نصب نسخه‌های قدیمی (توصیه نمی‌شود)
برای نصب نسخه خاصی از دستور زیر استفاده کنید. مثال برای نسخه `v1.7.9`:
```
VERSION=v1.7.9 && bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
```
## گواهی SSL
<details>
<summary>جزئیات گواهی SSL</summary>
### ACME
برای مدیریت گواهی‌های SSL با استفاده از ACME:
1. اطمینان حاصل کنید دامنه شما به درستی به سرور متصل است.
2. دستور `x-ui` را در ترمینال اجرا کرده و گزینه `مدیریت گواهی SSL` را انتخاب کنید.
3. گزینه‌های زیر نمایش داده می‌شوند:
- **دریافت SSL:** دریافت گواهی SSL
- **لغو:** لغو گواهی‌های موجود
- **تمدید اجباری:** تمدید اجباری گواهی‌ها
- **نمایش دامنه‌های موجود:** نمایش تمام دامنه‌های دارای گواهی
- **تنظیم مسیر گواهی برای پنل:** تنظیم مسیر گواهی برای دامنه شما
### Certbot
نصب و استفاده از Certbot:
```sh
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
### Cloudflare
اسکریپت داخلی برای دریافت گواهی SSL از Cloudflare. نیازمند:
- ایمیل ثبت‌شده در Cloudflare
- کلید API جهانی Cloudflare
- دامنه باید از طریق Cloudflare به سرور متصل باشد
**دریافت کلید API جهانی Cloudflare:**
1. دستور `x-ui` را اجرا و گزینه `گواهی SSL کلادفلر` را انتخاب کنید.
2. به لینک [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens) مراجعه کنید.
3. روی "View Global API Key" کلیک کنید:
![](media/APIKey1.PNG)
4. پس از احراز هویت، کلید API نمایش داده می‌شود:
![](media/APIKey2.png)
در هنگام استفاده، نام دامنه، ایمیل و کلید API را وارد کنید:
![](media/DetailEnter.png)
</details>
## نصب دستی و ارتقا
<details>
<summary>جزئیات نصب دستی</summary>
#### استفاده
1. دریافت آخرین نسخه از سرور:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. نصب یا ارتقا:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
## نصب با Docker
<details>
<summary>جزئیات Docker</summary>
#### استفاده
1. **نصب Docker:**
```sh
bash <(curl -sSL https://get.docker.com)
```
2. **کلون پروژه:**
```sh
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
```
3. **راه‌اندازی سرویس:**
```sh
docker compose up -d
```
یا
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/mhsanaei/3x-ui:latest
```
4. **به‌روزرسانی:**
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
5. **حذف:**
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## تنظیمات Nginx
<details>
<summary>پیکربندی Reverse Proxy</summary>
#### Nginx Reverse Proxy
```nginx
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_redirect off;
proxy_pass http://127.0.0.1:2053;
}
```
#### مسیر فرعی در Nginx
- اطمینان حاصل کنید "URI Path" در تنظیمات پنل یکسان باشد.
- `url` در تنظیمات پنل باید با `/` پایان یابد.
```nginx
location /sub {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_redirect off;
proxy_pass http://127.0.0.1:2053;
}
```
</details>
## سیستم‌عامل‌های توصیه شده
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- OpenEuler 22.03+
- Fedora 36+
- Arch Linux
- Parch Linux
- Manjaro
- Armbian
- AlmaLinux 8.0+
- Rocky Linux 8+
- Oracle Linux 8+
- OpenSUSE Tubleweed
- Amazon Linux 2023
- Windows x64
## معماری‌ها و دستگاه‌های پشتیبانی شده
<details>
<summary>جزئیات معماری‌ها و دستگاه‌ها</summary>
- **amd64**: معماری استاندارد برای کامپیوترهای شخصی و سرورها
- **x86 / i386**: سیستم‌های دسکتاپ و لپ‌تاپ
- **armv8 / arm64 / aarch64**: دستگاه‌های موبایل و embedded مانند Raspberry Pi 4
- **armv7 / arm / arm32**: دستگاه‌های قدیمی مانند Orange Pi Zero
- **armv6 / arm / arm32**: دستگاه‌های بسیار قدیمی مانند Raspberry Pi 1
- **armv5 / arm / arm32**: سیستم‌های embedded قدیمی
- **s390x**: کامپیوترهای IBM mainframe
</details>
## زبان‌های پشتیبانی شده
- انگلیسی
- فارسی
- چینی سنتی
- چینی ساده‌شده
- ژاپنی
- روسی
- ویتنامی
- اسپانیایی
- اندونزیایی
- اوکراینی
- ترکی
- پرتغالی (برزیل)
## ویژگی‌ها
- مانیتورینگ وضعیت سیستم
- جستجو در بین inboundها و کلاینت‌ها
- تم تاریک/روشن
- پشتیبانی از چند کاربر و پروتکل
- پروتکل‌های VMESS، VLESS، Trojan، Shadowsocks، Dokodemo-door، Socks، HTTP، WireGuard
- پشتیبانی از XTLS شامل RPRX-Direct، Vision، REALITY
- آمار ترافیک، محدودیت ترافیک، محدودیت زمانی
- تنظیمات سفارشی Xray
- پشتیبانی از HTTPS برای پنل
- دریافت خودکار گواهی SSL
- مسیرهای API اصلاح شده
- پشتیبانی از تغییر تنظیمات از طریق پنل
- امکان export/import دیتابیس
## تنظیمات پیش‌فرض پنل
<details>
<summary>جزئیات تنظیمات پیش‌فرض</summary>
### نام کاربری، رمز عبور، پورت و مسیر وب
در صورت عدم تغییر، این موارد به صورت تصادفی ایجاد می‌شوند (به جز Docker).
**تنظیمات پیش‌فرض Docker:**
- **نام کاربری:** admin
- **رمز عبور:** admin
- **پورت:** 2053
### مدیریت دیتابیس:
امکان Backup و Restore دیتابیس از طریق پنل.
- **مسیر دیتابیس:**
- `/etc/x-ui/x-ui.db`
### مسیر پایه وب
1. **بازنشانی مسیر:**
- اجرای دستور `x-ui`
- انتخاب گزینه `Reset Web Base Path`
2. **ساخت یا تنظیم مسیر:**
- مسیر به صورت تصادفی ساخته شده یا قابل تنظیم است
3. **مشاهده تنظیمات فعلی:**
- استفاده از دستور `x-ui settings` یا `View Current Settings` در `x-ui`
**توصیه امنیتی:**
- استفاده از مسیرهای طولانی و تصادفی برای افزایش امنیت
**مثال:**
- `http://ip:port/*webbasepath*/panel`
- `http://domain:port/*webbasepath*/panel`
</details>
## پیکربندی WARP
<details>
<summary>جزئیات WARP</summary>
#### استفاده
**برای نسخه‌های `v2.1.0` و جدیدتر:**
WARP به صورت داخلی پشتیبانی می‌شود. تنها نیاز به فعال‌سازی در پنل است.
</details>
## محدودیت IP
<details>
<summary>جزئیات محدودیت IP</summary>
#### استفاده
**توجه:** محدودیت IP در صورت استفاده از IP Tunnel کار نمی‌کند.
- **تا نسخه `v1.6.1`:**
- محدودیت IP به صورت داخلی در پنل وجود دارد
**برای نسخه‌های `v1.7.0` و جدیدتر:**
برای فعال‌سازی نیاز به نصب `fail2ban` است:
1. اجرای دستور `x-ui` و انتخاب `مدیریت محدودیت IP`
2. گزینه‌های موجود:
- **تغییر مدت زمان Ban**
- **حذف تمام Banها**
- **مشاهده لاگ‌ها**
- **وضعیت Fail2ban**
- **راه‌اندازی مجدد Fail2ban**
- **حذف Fail2ban**
3. تنظیم مسیر `Access log` در پنل به `./access.log` و ذخیره و راه‌اندازی مجدد Xray
- **قبل از نسخه `v2.1.3`:**
- تنظیم دستی `access.log` در تنظیمات Xray:
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
- **از نسخه `v2.1.3`:**
- امکان تنظیم `access.log` از طریق پنل
</details>
## ربات تلگرام
<details>
<summary>جزئیات ربات تلگرام</summary>
#### استفاده
ربات تلگرام برای اطلاع‌رسانی ترافیک، ورود به پنل، Backup دیتابیس و ... استفاده می‌شود. نیازمند تنظیم:
- توکن تلگرام
- Chat ID ادمین‌ها
- زمان اطلاع‌رسانی (Cron syntax)
- اطلاع‌رسانی انقضا
- اطلاع‌رسانی ترافیک
- Backup دیتابیس
- اطلاع‌رسانی مصرف CPU
**سینتکس نمونه:**
- `30 \* \* \* \* \*` - اطلاع در ثانیه 30 هر دقیقه
- `@hourly` - هر ساعت
- `@daily` - هر روز
### ویژگی‌های ربات
- گزارش دوره‌ای
- اطلاع ورود به پنل
- اطلاع مصرف CPU
- اطلاع پیش‌از موعد انقضا و ترافیک
- گزارش ترافیک کلاینت‌ها
- منوی مبتنی بر دستور
- جستجوی کلاینت بر اساس ایمیل
- بررسی inboundها
- بررسی وضعیت سرور
- دریافت Backup
- چندزبانه
### راه‌اندازی ربات
- شروع [Botfather](https://t.me/BotFather) در تلگرام:
![Botfather](./media/botfather.png)
- ساخت ربات جدید با دستور /newbot:
![Create new bot](./media/newbot.png)
- شروع ربات ساخته شده:
![token](./media/token.png)
- تنظیمات پنل:
![Panel Config](./media/panel-bot-config.png)
وارد کردن توکن و Chat ID (دریافت از [این ربات](https://t.me/useridinfobot)):
![User ID](./media/user-id.png)
</details>
## مسیرهای API
<details>
<summary>جزئیات API</summary>
#### استفاده
- [مستندات API](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
- `/login` با `POST` داده کاربر: `{username: '', password: ''}`
| Method | مسیر | عملکرد |
| :----: | ---------------------------------- | ------------------------------------------- |
| `GET` | `"/list"` | دریافت تمام inboundها |
| `GET` | `"/get/:id"` | دریافت inbound بر اساس id |
| `POST` | `"/add"` | افزودن inbound |
| `POST` | `"/del/:id"` | حذف inbound |
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
</details>
## متغیرهای محیطی
<details>
<summary>جزئیات متغیرها</summary>
#### استفاده
| متغیر | نوع | پیش‌فرض |
| ------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER| `string` | `"bin"` |
مثال:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
## پیش‌نمایش
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
<img alt="3x-ui" src="./media/01-overview-light.png">
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
<img alt="3x-ui" src="./media/02-inbounds-light.png">
</picture>
## قدردانی ویژه از
- [alireza0](https://github.com/alireza0/)
## تشکر و قدردانی
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**)
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**)
## Stargazers over Time
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,4 +1,4 @@
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) [English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center"> <p align="center">
<picture> <picture>
@@ -578,7 +578,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
<img alt="3x-ui" src="./media/06-configs-light.png"> <img alt="3x-ui" src="./media/06-configs-light.png">
</picture> </picture>
<picture> <picture>
<img alt="3x-ui" src="./media/7.png"> <source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
<img alt="3x-ui" src="./media/07-bot-light.png">
</picture> </picture>
## A Special Thanks to ## A Special Thanks to
@@ -588,8 +589,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## Acknowledgment ## Acknowledgment
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
## Stargazers over Time ## Stargazers over Time
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) [![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,4 +1,4 @@
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) [English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center"> <p align="center">
<picture> <picture>
@@ -576,7 +576,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
<img alt="3x-ui" src="./media/06-configs-light.png"> <img alt="3x-ui" src="./media/06-configs-light.png">
</picture> </picture>
<picture> <picture>
<img alt="3x-ui" src="./media/7.png"> <source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
<img alt="3x-ui" src="./media/07-bot-light.png">
</picture> </picture>
## Особая благодарность ## Особая благодарность
@@ -586,8 +587,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## Благодарности ## Благодарности
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _Этот репозиторий содержит автоматически обновляемые правила маршрутизации V2Ray на основе данных о заблокированных доменах и адресах в России._
## Число звёзд со временем ## Число звёзд со временем
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) [![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,4 +1,4 @@
[English](/README.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md) [English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center"> <p align="center">
<picture> <picture>
@@ -569,7 +569,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
<img alt="3x-ui" src="./media/06-configs-light.png"> <img alt="3x-ui" src="./media/06-configs-light.png">
</picture> </picture>
<picture> <picture>
<img alt="3x-ui" src="./media/7.png"> <source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
<img alt="3x-ui" src="./media/07-bot-light.png">
</picture> </picture>
## 特别感谢 ## 特别感谢
@@ -579,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## 致谢 ## 致谢
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._ - [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._ - [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
## Star趋势 ## Star趋势
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui) [![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1 +1 @@
2.4.9 2.5.5

View File

@@ -98,5 +98,6 @@ type Client struct {
Enable bool `json:"enable" form:"enable"` Enable bool `json:"enable" form:"enable"`
TgID int64 `json:"tgId" form:"tgId"` TgID int64 `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"` SubID string `json:"subId" form:"subId"`
Comment string `json:"comment" form:"comment"`
Reset int `json:"reset" form:"reset"` Reset int `json:"reset" form:"reset"`
} }

View File

@@ -11,6 +11,7 @@ services:
- $PWD/cert/:/root/cert/ - $PWD/cert/:/root/cert/
environment: environment:
XRAY_VMESS_AEAD_FORCED: "false" XRAY_VMESS_AEAD_FORCED: "false"
X_UI_ENABLE_FAIL2BAN: "true"
tty: true tty: true
network_mode: host network_mode: host
restart: unless-stopped restart: unless-stopped

89
go.mod
View File

@@ -1,46 +1,45 @@
module x-ui module x-ui
go 1.23.4 go 1.24.1
require ( require (
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.2.2
github.com/gin-contrib/sessions v1.0.1 github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.4 github.com/goccy/go-json v0.10.5
github.com/mymmrac/telego v0.31.4 github.com/mymmrac/telego v0.32.0
github.com/nicksnyder/go-i18n/v2 v2.4.1 github.com/nicksnyder/go-i18n/v2 v2.5.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.3 github.com/pelletier/go-toml/v2 v2.2.3
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.24.11 github.com/shirou/gopsutil/v4 v4.25.2
github.com/valyala/fasthttp v1.58.0 github.com/valyala/fasthttp v1.59.0
github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4 github.com/xtls/xray-core v1.250306.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.21.0 golang.org/x/text v0.23.0
google.golang.org/grpc v1.69.0 google.golang.org/grpc v1.71.0
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
require ( require (
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bytedance/sonic v1.12.6 // indirect github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudflare/circl v1.5.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/ebitengine/purego v0.8.2 // indirect
github.com/ebitengine/purego v0.8.1 // indirect github.com/fasthttp/router v1.5.4 // indirect
github.com/fasthttp/router v1.5.3 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gin-contrib/sse v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.3.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/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect github.com/gorilla/sessions v1.4.0 // indirect
@@ -49,29 +48,29 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.22.0 // indirect github.com/onsi/ginkgo/v2 v2.23.0 // indirect
github.com/pires/go-proxyproto v0.8.0 // indirect github.com/pires/go-proxyproto v0.8.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.48.2 // indirect github.com/quic-go/quic-go v0.50.0 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect github.com/refraction-networking/utls v1.6.7 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.5.1 // indirect github.com/sagernet/sing v0.6.3 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
@@ -83,20 +82,20 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.5.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.12.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.32.0 // indirect golang.org/x/net v0.37.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.31.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/protobuf v1.35.2 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.4.0 // indirect
) )

209
go.sum
View File

@@ -4,38 +4,37 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/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/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-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-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fasthttp/router v1.5.3 h1:BFWXqa3e4thRI3MgPKTNtz0Oiq6UYN2OsEtb+YQ5TMI= github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
github.com/fasthttp/router v1.5.3/go.mod h1:b864KkDIapOYh77AVG/SNkwfRZ6k6ecWvD+ZRXmP5pw= github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
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 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= 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.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4= github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 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/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@@ -51,25 +50,25 @@ 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 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-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= 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/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/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 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
@@ -88,11 +87,11 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -100,27 +99,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.31.4 h1:NpiNl0P/8eydknka/k6XaaaWVj5BKMlM3Ibba63QTBU= github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
github.com/mymmrac/telego v0.31.4/go.mod h1:T12js1PgbYDYznvoN05MSMuPMfWTYo7D9LKl5cPFWiI= github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g= github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk= github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= 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/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
@@ -135,41 +134,43 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/refraction-networking/utls v1.6.7/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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 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/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8= github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk=
github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 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/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 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -178,8 +179,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
@@ -189,64 +190,66 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM= github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg= github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4 h1:zdd86FEjFZjAaRbWxiZQM2QPOzk/d6cig2DaE7c3MDQ= github.com/xtls/xray-core v1.250306.0 h1:XZyZvSgcpAoVEGnFnxNdoHbSF7Kp77A/0TPk4lhv6rM=
github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4/go.mod h1:lduNPDkXku+Avphl8g7W0yJrHhWyxdOnPo0XGYdF0Aw= github.com/xtls/xray-core v1.250306.0/go.mod h1:clXnUOnX6CKWBGgJY4ePYhb/EtTdSrUC7vPfT6m5p4c=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 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/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 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= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 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/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 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -260,8 +263,8 @@ gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -2,6 +2,7 @@
red='\033[0;31m' red='\033[0;31m'
green='\033[0;32m' green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m' yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
@@ -89,6 +90,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "virtuozzo" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Virtuozzo Linux 8 or higher ${plain}\n" && exit 1
fi
else else
echo -e "${red}Your operating system is not supported by this script.${plain}\n" echo -e "${red}Your operating system is not supported by this script.${plain}\n"
echo "Please ensure you are using one of the following supported operating systems:" echo "Please ensure you are using one of the following supported operating systems:"
@@ -106,6 +111,7 @@ else
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed" echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023" echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1 exit 1
fi fi
@@ -117,7 +123,7 @@ install_base() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum install -y -q wget curl tar tzdata yum -y update && yum install -y -q wget curl tar tzdata
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf install -y -q wget curl tar tzdata dnf -y update && dnf install -y -q wget curl tar tzdata
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -208,7 +214,7 @@ install_x-ui() {
exit 1 exit 1
fi fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1 exit 1
@@ -225,7 +231,7 @@ install_x-ui() {
url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz" url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui $1" echo -e "Beginning to install x-ui $1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url} wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}" echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1 exit 1
@@ -250,7 +256,7 @@ install_x-ui() {
chmod +x x-ui bin/xray-linux-$(arch) chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/ cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
config_after_install config_after_install
@@ -260,24 +266,24 @@ install_x-ui() {
systemctl start x-ui systemctl start x-ui
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..." echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
echo -e "" echo -e ""
echo -e "x-ui control menu usages: " echo -e "┌───────────────────────────────────────────────────────┐
echo -e "----------------------------------------------" ${blue}x-ui control menu usages (subcommands):${plain}
echo -e "SUBCOMMANDS:" │ │
echo -e "x-ui - Admin Management Script" ${blue}x-ui${plain} - Admin Management Script
echo -e "x-ui start - Start" ${blue}x-ui start${plain} - Start │
echo -e "x-ui stop - Stop" ${blue}x-ui stop${plain} - Stop │
echo -e "x-ui restart - Restart" ${blue}x-ui restart${plain} - Restart │
echo -e "x-ui status - Current Status" ${blue}x-ui status${plain} - Current Status
echo -e "x-ui settings - Current Settings" ${blue}x-ui settings${plain} - Current Settings
echo -e "x-ui enable - Enable Autostart on OS Startup" ${blue}x-ui enable${plain} - Enable Autostart on OS Startup
echo -e "x-ui disable - Disable Autostart on OS Startup" ${blue}x-ui disable${plain} - Disable Autostart on OS Startup
echo -e "x-ui log - Check logs" ${blue}x-ui log${plain} - Check logs │
echo -e "x-ui banlog - Check Fail2ban ban logs" ${blue}x-ui banlog${plain} - Check Fail2ban ban logs
echo -e "x-ui update - Update" ${blue}x-ui update${plain} - Update │
echo -e "x-ui legacy - legacy version" ${blue}x-ui legacy${plain} - legacy version
echo -e "x-ui install - Install" ${blue}x-ui install${plain} - Install │
echo -e "x-ui uninstall - Uninstall" ${blue}x-ui uninstall${plain} - Uninstall │
echo -e "----------------------------------------------" └───────────────────────────────────────────────────────┘"
} }
echo -e "${green}Running...${plain}" echo -e "${green}Running...${plain}"

BIN
media/07-bot-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
media/07-bot-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -21,7 +21,7 @@ type SubJsonService struct {
configJson map[string]interface{} configJson map[string]interface{}
defaultOutbounds []json_util.RawMessage defaultOutbounds []json_util.RawMessage
fragment string fragment string
noises string noises string
mux string mux string
inboundService service.InboundService inboundService service.InboundService
@@ -61,7 +61,7 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
configJson: configJson, configJson: configJson,
defaultOutbounds: defaultOutbounds, defaultOutbounds: defaultOutbounds,
fragment: fragment, fragment: fragment,
noises: noises, noises: noises,
mux: mux, mux: mux,
SubService: subService, SubService: subService,
} }
@@ -217,7 +217,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
delete(streamSettings, "sockopt") delete(streamSettings, "sockopt")
if s.fragment != "" { if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "tcpNoDelay": true}`) streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "penetrate": true}`)
} }
// remove proxy protocol // remove proxy protocol

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(global):typeof define==="function"&&define.amd?define(factory):factory(global)})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this,function(global){"use strict";var _Base64=global.Base64;var version="2.5.0";var buffer;if(typeof module!=="undefined"&&module.exports){try{buffer=eval("require('buffer').Buffer")}catch(err){buffer=undefined}}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(u){return(u.constructor===buffer.constructor?u:buffer.from(u)).toString("base64")}:function(u){return(u.constructor===buffer.constructor?u:new buffer(u)).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(String(u)):_encode(String(u)).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][€-¿]","[à-ï][€-¿]{2}","[ð-÷][€-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var _atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/\S{1,4}/g,cb_decode)};var atob=function(a){return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g,""))};var _decode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(a){return(a.constructor===buffer.constructor?a:buffer.from(a,"base64")).toString()}:function(a){return(a.constructor===buffer.constructor?a:new buffer(a,"base64")).toString()}:function(a){return btou(_atob(a))};var decode=function(a){return _decode(String(a).replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict,__buffer__:buffer};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}if(global["Meteor"]){Base64=global.Base64}if(typeof module!=="undefined"&&module.exports){module.exports.Base64=global.Base64}else if(typeof define==="function"&&define.amd){define([],function(){return global.Base64})}return{Base64:global.Base64}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,103 +0,0 @@
const supportLangs = [
{
name: "English",
value: "en-US",
icon: "🇺🇸",
},
{
name: "فارسی",
value: "fa-IR",
icon: "🇮🇷",
},
{
name: "简体中文",
value: "zh-CN",
icon: "🇨🇳",
},
{
name: "繁體中文",
value: "zh-TW",
icon: "🇹🇼",
},
{
name: "日本語",
value: "ja-JP",
icon: "🇯🇵",
},
{
name: "Русский",
value: "ru-RU",
icon: "🇷🇺",
},
{
name: "Tiếng Việt",
value: "vi-VN",
icon: "🇻🇳",
},
{
name: "Español",
value: "es-ES",
icon: "🇪🇸",
},
{
name: "Indonesian",
value: "id-ID",
icon: "🇮🇩",
},
{
name: "Український",
value: "uk-UA",
icon: "🇺🇦",
},
{
name: "Türkçe",
value: "tr-TR",
icon: "🇹🇷",
},
{
name: "Português",
value: "pt-BR",
icon: "🇧🇷",
},
];
function getLang() {
let lang = getCookie("lang");
if (!lang) {
if (window.navigator) {
lang = window.navigator.language || window.navigator.userLanguage;
if (isSupportLang(lang)) {
setCookie("lang", lang, 150);
} else {
setCookie("lang", "en-US", 150);
window.location.reload();
}
} else {
setCookie("lang", "en-US", 150);
window.location.reload();
}
}
return lang;
}
function setLang(lang) {
if (!isSupportLang(lang)) {
lang = "en-US";
}
setCookie("lang", lang, 150);
window.location.reload();
}
function isSupportLang(lang) {
for (l of supportLangs) {
if (l.value === lang) {
return true;
}
}
return false;
}

View File

@@ -25,11 +25,11 @@ class DBInbound {
} }
get totalGB() { get totalGB() {
return toFixed(this.total / ONE_GB, 2); return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
} }
set totalGB(gb) { set totalGB(gb) {
this.total = toFixed(gb * ONE_GB, 0); this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
get isVMess() { get isVMess() {

View File

@@ -34,10 +34,6 @@ const TLS_VERSION_OPTION = {
}; };
const TLS_CIPHER_OPTION = { const TLS_CIPHER_OPTION = {
RSA_AES_128_CBC: "TLS_RSA_WITH_AES_128_CBC_SHA",
RSA_AES_256_CBC: "TLS_RSA_WITH_AES_256_CBC_SHA",
RSA_AES_128_GCM: "TLS_RSA_WITH_AES_128_GCM_SHA256",
RSA_AES_256_GCM: "TLS_RSA_WITH_AES_256_GCM_SHA384",
AES_128_GCM: "TLS_AES_128_GCM_SHA256", AES_128_GCM: "TLS_AES_128_GCM_SHA256",
AES_256_GCM: "TLS_AES_256_GCM_SHA384", AES_256_GCM: "TLS_AES_256_GCM_SHA384",
CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256", CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256",
@@ -64,6 +60,8 @@ const UTLS_FINGERPRINT = {
UTLS_QQ: "qq", UTLS_QQ: "qq",
UTLS_RANDOM: "random", UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe",
}; };
const ALPN_OPTION = { const ALPN_OPTION = {
@@ -496,19 +494,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
headers = [], headers = [],
scMaxBufferedPosts = 30, scMaxBufferedPosts = 30,
scMaxEachPostBytes = "1000000", scMaxEachPostBytes = "1000000",
scMinPostsIntervalMs = "30", scStreamUpServerSecs = "20-80",
noSSEHeader = false, noSSEHeader = false,
xPaddingBytes = "100-1000", xPaddingBytes = "100-1000",
xmux = {
maxConcurrency: "16-32",
maxConnections: 0,
cMaxReuseTimes: "64-128",
cMaxLifetimeMs: 0,
hMaxRequestTimes: "800-900",
hKeepAlivePeriod: 0,
},
mode = MODE_OPTION.AUTO, mode = MODE_OPTION.AUTO,
noGRPCHeader = false
) { ) {
super(); super();
this.path = path; this.path = path;
@@ -516,12 +505,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
this.headers = headers; this.headers = headers;
this.scMaxBufferedPosts = scMaxBufferedPosts; this.scMaxBufferedPosts = scMaxBufferedPosts;
this.scMaxEachPostBytes = scMaxEachPostBytes; this.scMaxEachPostBytes = scMaxEachPostBytes;
this.scMinPostsIntervalMs = scMinPostsIntervalMs; this.scStreamUpServerSecs = scStreamUpServerSecs;
this.noSSEHeader = noSSEHeader; this.noSSEHeader = noSSEHeader;
this.xPaddingBytes = xPaddingBytes; this.xPaddingBytes = xPaddingBytes;
this.xmux = xmux;
this.mode = mode; this.mode = mode;
this.noGRPCHeader = noGRPCHeader;
} }
addHeader(name, value) { addHeader(name, value) {
@@ -539,12 +526,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
XrayCommonClass.toHeaders(json.headers), XrayCommonClass.toHeaders(json.headers),
json.scMaxBufferedPosts, json.scMaxBufferedPosts,
json.scMaxEachPostBytes, json.scMaxEachPostBytes,
json.scMinPostsIntervalMs, json.scStreamUpServerSecs,
json.noSSEHeader, json.noSSEHeader,
json.xPaddingBytes, json.xPaddingBytes,
json.xmux,
json.mode, json.mode,
json.noGRPCHeader,
); );
} }
@@ -555,19 +540,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
headers: XrayCommonClass.toV2Headers(this.headers, false), headers: XrayCommonClass.toV2Headers(this.headers, false),
scMaxBufferedPosts: this.scMaxBufferedPosts, scMaxBufferedPosts: this.scMaxBufferedPosts,
scMaxEachPostBytes: this.scMaxEachPostBytes, scMaxEachPostBytes: this.scMaxEachPostBytes,
scMinPostsIntervalMs: this.scMinPostsIntervalMs, scStreamUpServerSecs: this.scStreamUpServerSecs,
noSSEHeader: this.noSSEHeader, noSSEHeader: this.noSSEHeader,
xPaddingBytes: this.xPaddingBytes, xPaddingBytes: this.xPaddingBytes,
xmux: {
maxConcurrency: this.xmux.maxConcurrency,
maxConnections: this.xmux.maxConnections,
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
cMaxLifetimeMs: this.xmux.cMaxLifetimeMs,
hMaxRequestTimes: this.xmux.hMaxRequestTimes,
hKeepAlivePeriod: this.xmux.hKeepAlivePeriod,
},
mode: this.mode, mode: this.mode,
noGRPCHeader: this.noGRPCHeader,
}; };
} }
} }
@@ -579,6 +555,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13, maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '', cipherSuites = '',
rejectUnknownSni = false, rejectUnknownSni = false,
verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
disableSystemRoot = false, disableSystemRoot = false,
enableSessionResumption = false, enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()], certificates = [new TlsStreamSettings.Cert()],
@@ -591,6 +568,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion; this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites; this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni; this.rejectUnknownSni = rejectUnknownSni;
this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
this.disableSystemRoot = disableSystemRoot; this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption; this.enableSessionResumption = enableSessionResumption;
this.certs = certificates; this.certs = certificates;
@@ -622,6 +600,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion, json.maxVersion,
json.cipherSuites, json.cipherSuites,
json.rejectUnknownSni, json.rejectUnknownSni,
json.verifyPeerCertInNames,
json.disableSystemRoot, json.disableSystemRoot,
json.enableSessionResumption, json.enableSessionResumption,
certs, certs,
@@ -637,6 +616,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion, maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites, cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni, rejectUnknownSni: this.rejectUnknownSni,
verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
disableSystemRoot: this.disableSystemRoot, disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption, enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
@@ -718,7 +698,10 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
}; };
TlsStreamSettings.Settings = class extends XrayCommonClass { TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, fingerprint = '') { constructor(
allowInsecure = false,
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
) {
super(); super();
this.allowInsecure = allowInsecure; this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
@@ -784,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass {
json.maxClient, json.maxClient,
json.maxTimediff, json.maxTimediff,
json.shortIds, json.shortIds,
json.settings, settings,
); );
} }
@@ -807,7 +790,7 @@ class RealityStreamSettings extends XrayCommonClass {
RealityStreamSettings.Settings = class extends XrayCommonClass { RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor( constructor(
publicKey = '', publicKey = '',
fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
serverName = '', serverName = '',
spiderX = '/' spiderX = '/'
) { ) {
@@ -842,7 +825,7 @@ class SockoptStreamSettings extends XrayCommonClass {
mark = 0, mark = 0,
tproxy = "off", tproxy = "off",
tcpMptcp = false, tcpMptcp = false,
tcpNoDelay = false, penetrate = false,
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP, domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
tcpMaxSeg = 1440, tcpMaxSeg = 1440,
dialerProxy = "", dialerProxy = "",
@@ -860,7 +843,7 @@ class SockoptStreamSettings extends XrayCommonClass {
this.mark = mark; this.mark = mark;
this.tproxy = tproxy; this.tproxy = tproxy;
this.tcpMptcp = tcpMptcp; this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay; this.penetrate = penetrate;
this.domainStrategy = domainStrategy; this.domainStrategy = domainStrategy;
this.tcpMaxSeg = tcpMaxSeg; this.tcpMaxSeg = tcpMaxSeg;
this.dialerProxy = dialerProxy; this.dialerProxy = dialerProxy;
@@ -881,7 +864,7 @@ class SockoptStreamSettings extends XrayCommonClass {
json.mark, json.mark,
json.tproxy, json.tproxy,
json.tcpMptcp, json.tcpMptcp,
json.tcpNoDelay, json.penetrate,
json.domainStrategy, json.domainStrategy,
json.tcpMaxSeg, json.tcpMaxSeg,
json.dialerProxy, json.dialerProxy,
@@ -902,7 +885,7 @@ class SockoptStreamSettings extends XrayCommonClass {
mark: this.mark, mark: this.mark,
tproxy: this.tproxy, tproxy: this.tproxy,
tcpMptcp: this.tcpMptcp, tcpMptcp: this.tcpMptcp,
tcpNoDelay: this.tcpNoDelay, penetrate: this.penetrate,
domainStrategy: this.domainStrategy, domainStrategy: this.domainStrategy,
tcpMaxSeg: this.tcpMaxSeg, tcpMaxSeg: this.tcpMaxSeg,
dialerProxy: this.dialerProxy, dialerProxy: this.dialerProxy,
@@ -1067,7 +1050,7 @@ class Allocate extends XrayCommonClass {
class Inbound extends XrayCommonClass { class Inbound extends XrayCommonClass {
constructor( constructor(
port = RandomUtil.randomIntRange(10000, 60000), port = RandomUtil.randomInteger(10000, 60000),
listen = '', listen = '',
protocol = Protocols.VLESS, protocol = Protocols.VLESS,
settings = null, settings = null,
@@ -1243,7 +1226,7 @@ class Inbound extends XrayCommonClass {
} }
reset() { reset() {
this.port = RandomUtil.randomIntRange(10000, 60000); this.port = RandomUtil.randomInteger(10000, 60000);
this.listen = ''; this.listen = '';
this.protocol = Protocols.VMESS; this.protocol = Protocols.VMESS;
this.settings = Inbound.Settings.getSettings(Protocols.VMESS); this.settings = Inbound.Settings.getSettings(Protocols.VMESS);
@@ -1304,7 +1287,7 @@ class Inbound extends XrayCommonClass {
obj.mode = xhttp.mode; obj.mode = xhttp.mode;
} }
if (security === 'tls') { if (tls === 'tls') {
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) { if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
obj.sni = this.stream.tls.sni; obj.sni = this.stream.tls.sni;
} }
@@ -1319,7 +1302,7 @@ class Inbound extends XrayCommonClass {
} }
} }
return 'vmess://' + base64(JSON.stringify(obj, null, 2)); return 'vmess://' + Base64.encode(JSON.stringify(obj, null, 2));
} }
genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) { genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) {
@@ -1491,7 +1474,7 @@ class Inbound extends XrayCommonClass {
if (this.isSS2022) password.push(settings.password); if (this.isSS2022) password.push(settings.password);
if (this.isSSMultiUser) password.push(clientPassword); if (this.isSSMultiUser) password.push(clientPassword);
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`; let link = `ss://${Base64.encode(`${settings.method}:${password.join(':')}`, true)}@${address}:${port}`;
const url = new URL(link); const url = new URL(link);
for (const [key, value] of params) { for (const [key, value] of params) {
url.searchParams.set(key, value) url.searchParams.set(key, value)
@@ -1804,6 +1787,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0 reset = 0
) { ) {
super(); super();
@@ -1816,6 +1800,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.comment = comment;
this.reset = reset; this.reset = reset;
} }
@@ -1830,6 +1815,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.comment,
json.reset, json.reset,
); );
} }
@@ -1851,11 +1837,11 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { set _totalGB(gb) {
this.totalGB = toFixed(gb * ONE_GB, 0); this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
}; };
@@ -1910,6 +1896,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0 reset = 0
) { ) {
super(); super();
@@ -1922,6 +1909,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.comment = comment;
this.reset = reset; this.reset = reset;
} }
@@ -1936,6 +1924,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.comment,
json.reset, json.reset,
); );
} }
@@ -1958,11 +1947,11 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { set _totalGB(gb) {
this.totalGB = toFixed(gb * ONE_GB, 0); this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
}; };
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
@@ -2046,6 +2035,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0 reset = 0
) { ) {
super(); super();
@@ -2057,6 +2047,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.comment = comment;
this.reset = reset; this.reset = reset;
} }
@@ -2070,6 +2061,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable: this.enable, enable: this.enable,
tgId: this.tgId, tgId: this.tgId,
subId: this.subId, subId: this.subId,
comment: this.comment,
reset: this.reset, reset: this.reset,
}; };
} }
@@ -2084,6 +2076,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.comment,
json.reset, json.reset,
); );
} }
@@ -2106,11 +2099,11 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { set _totalGB(gb) {
this.totalGB = toFixed(gb * ONE_GB, 0); this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
}; };
@@ -2203,6 +2196,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0 reset = 0
) { ) {
super(); super();
@@ -2215,6 +2209,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.comment = comment;
this.reset = reset; this.reset = reset;
} }
@@ -2229,6 +2224,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable: this.enable, enable: this.enable,
tgId: this.tgId, tgId: this.tgId,
subId: this.subId, subId: this.subId,
comment: this.comment,
reset: this.reset, reset: this.reset,
}; };
} }
@@ -2244,6 +2240,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.comment,
json.reset, json.reset,
); );
} }
@@ -2266,11 +2263,11 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { set _totalGB(gb) {
this.totalGB = toFixed(gb * ONE_GB, 0); this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
}; };

View File

@@ -39,6 +39,8 @@ const UTLS_FINGERPRINT = {
UTLS_QQ: "qq", UTLS_QQ: "qq",
UTLS_RANDOM: "random", UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe",
}; };
const ALPN_OPTION = { const ALPN_OPTION = {
@@ -84,6 +86,16 @@ const MODE_OPTION = {
STREAM_ONE: "stream-one", STREAM_ONE: "stream-one",
}; };
const Address_Port_Strategy = {
NONE: "none",
SrvPortOnly: "srvportonly",
SrvAddressOnly: "srvaddressonly",
SrvPortAndAddress: "srvportandaddress",
TxtPortOnly: "txtportonly",
TxtAddressOnly: "txtaddressonly",
TxtPortAndAddress: "txtportandaddress"
};
Object.freeze(Protocols); Object.freeze(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
@@ -93,7 +105,7 @@ Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy); Object.freeze(WireguardDomainStrategy);
Object.freeze(USERS_SECURITY); Object.freeze(USERS_SECURITY);
Object.freeze(MODE_OPTION); Object.freeze(MODE_OPTION);
Object.freeze(Address_Port_Strategy);
class CommonClass { class CommonClass {
@@ -287,11 +299,24 @@ class xHTTPStreamSettings extends CommonClass {
path = '/', path = '/',
host = '', host = '',
mode = '', mode = '',
noGRPCHeader = false,
scMinPostsIntervalMs = "30",
xmux = {
maxConcurrency: "16-32",
maxConnections: 0,
cMaxReuseTimes: 0,
hMaxRequestTimes: "600-900",
hMaxReusableSecs: "1800-3000",
hKeepAlivePeriod: 0,
},
) { ) {
super(); super();
this.path = path; this.path = path;
this.host = host; this.host = host;
this.mode = mode; this.mode = mode;
this.noGRPCHeader = noGRPCHeader;
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
this.xmux = xmux;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -299,6 +324,9 @@ class xHTTPStreamSettings extends CommonClass {
json.path, json.path,
json.host, json.host,
json.mode, json.mode,
json.noGRPCHeader,
json.scMinPostsIntervalMs,
json.xmux
); );
} }
@@ -307,6 +335,16 @@ class xHTTPStreamSettings extends CommonClass {
path: this.path, path: this.path,
host: this.host, host: this.host,
mode: this.mode, mode: this.mode,
noGRPCHeader: this.noGRPCHeader,
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
xmux: {
maxConcurrency: this.xmux.maxConcurrency,
maxConnections: this.xmux.maxConnections,
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
hMaxRequestTimes: this.xmux.hMaxRequestTimes,
hMaxReusableSecs: this.xmux.hMaxReusableSecs,
hKeepAlivePeriod: this.xmux.hKeepAlivePeriod,
},
}; };
} }
} }
@@ -384,14 +422,16 @@ class SockoptStreamSettings extends CommonClass {
tcpFastOpen = false, tcpFastOpen = false,
tcpKeepAliveInterval = 0, tcpKeepAliveInterval = 0,
tcpMptcp = false, tcpMptcp = false,
tcpNoDelay = false penetrate = false,
addressPortStrategy = Address_Port_Strategy.NONE,
) { ) {
super(); super();
this.dialerProxy = dialerProxy; this.dialerProxy = dialerProxy;
this.tcpFastOpen = tcpFastOpen; this.tcpFastOpen = tcpFastOpen;
this.tcpKeepAliveInterval = tcpKeepAliveInterval; this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpMptcp = tcpMptcp; this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay; this.penetrate = penetrate;
this.addressPortStrategy = addressPortStrategy;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -401,7 +441,8 @@ class SockoptStreamSettings extends CommonClass {
json.tcpFastOpen, json.tcpFastOpen,
json.tcpKeepAliveInterval, json.tcpKeepAliveInterval,
json.tcpMptcp, json.tcpMptcp,
json.tcpNoDelay, json.penetrate,
json.addressPortStrategy
); );
} }
@@ -411,7 +452,8 @@ class SockoptStreamSettings extends CommonClass {
tcpFastOpen: this.tcpFastOpen, tcpFastOpen: this.tcpFastOpen,
tcpKeepAliveInterval: this.tcpKeepAliveInterval, tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpMptcp: this.tcpMptcp, tcpMptcp: this.tcpMptcp,
tcpNoDelay: this.tcpNoDelay, penetrate: this.penetrate,
addressPortStrategy: this.addressPortStrategy
}; };
} }
} }
@@ -694,6 +736,7 @@ class Outbound extends CommonClass {
let headerType = url.searchParams.get('headerType') ?? undefined; let headerType = url.searchParams.get('headerType') ?? undefined;
let host = url.searchParams.get('host') ?? undefined; let host = url.searchParams.get('host') ?? undefined;
let path = url.searchParams.get('path') ?? undefined; let path = url.searchParams.get('path') ?? undefined;
let mode = url.searchParams.get('mode') ?? undefined;
if (type === 'tcp' || type === 'none') { if (type === 'tcp' || type === 'none') {
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path); stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);

View File

@@ -31,6 +31,8 @@ class AllSetting {
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/"; this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.externalTrafficInformEnable = false;
this.externalTrafficInformURI = "";
this.subCertFile = ""; this.subCertFile = "";
this.subKeyFile = ""; this.subKeyFile = "";
this.subUpdates = 12; this.subUpdates = 12;
@@ -43,7 +45,7 @@ class AllSetting {
this.subJsonMux = ""; this.subJsonMux = "";
this.subJsonRules = ""; this.subJsonRules = "";
this.timeLocation = "Asia/Tehran"; this.timeLocation = "Local";
if (data == null) { if (data == null) {
return return

View File

@@ -1,194 +0,0 @@
const ONE_KB = 1024;
const ONE_MB = ONE_KB * 1024;
const ONE_GB = ONE_MB * 1024;
const ONE_TB = ONE_GB * 1024;
const ONE_PB = ONE_TB * 1024;
function sizeFormat(size) {
if (size <= 0) return "0 B";
if (size < ONE_KB) {
return size.toFixed(0) + " B";
} else if (size < ONE_MB) {
return (size / ONE_KB).toFixed(2) + " KB";
} else if (size < ONE_GB) {
return (size / ONE_MB).toFixed(2) + " MB";
} else if (size < ONE_TB) {
return (size / ONE_GB).toFixed(2) + " GB";
} else if (size < ONE_PB) {
return (size / ONE_TB).toFixed(2) + " TB";
} else {
return (size / ONE_PB).toFixed(2) + " PB";
}
}
function cpuSpeedFormat(speed) {
if (speed > 1000) {
const GHz = speed / 1000;
return GHz.toFixed(2) + " GHz";
} else {
return speed.toFixed(2) + " MHz";
}
}
function cpuCoreFormat(cores) {
if (cores === 1) {
return "1 Core";
} else {
return cores + " Cores";
}
}
function base64(str) {
return Base64.encode(str);
}
function safeBase64(str) {
return base64(str)
.replace(/\+/g, '-')
.replace(/=/g, '')
.replace(/\//g, '_');
}
function formatSecond(second) {
if (second < 60) {
return second.toFixed(0) + 's';
} else if (second < 3600) {
return (second / 60).toFixed(0) + 'm';
} else if (second < 3600 * 24) {
return (second / 3600).toFixed(0) + 'h';
} else {
day = Math.floor(second / 3600 / 24);
remain = ((second / 3600) - (day * 24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
function addZero(num) {
if (num < 10) {
return "0" + num;
} else {
return num;
}
}
function toFixed(num, n) {
n = Math.pow(10, n);
return Math.floor(num * n) / n;
}
function debounce(fn, delay) {
var timeoutID = null;
return function () {
clearTimeout(timeoutID);
var args = arguments;
var that = this;
timeoutID = setTimeout(function () {
fn.apply(that, args);
}, delay);
};
}
function getCookie(cname) {
let name = cname + '=';
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
// decode cookie value only
return decodeURIComponent(c.substring(name.length, c.length));
}
}
return '';
}
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = 'expires=' + d.toUTCString();
// encode cookie value
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
}
function usageColor(data, threshold, total) {
switch (true) {
case data === null:
return "purple";
case total < 0:
return "green";
case total == 0:
return "purple";
case data < total - threshold:
return "green";
case data < total:
return "orange";
default:
return "red";
}
}
function clientUsageColor(clientStats, trafficDiff) {
switch (true) {
case !clientStats || clientStats.total == 0:
return "#7a316f"; // purple
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
return "#008771"; // Green
case clientStats.up + clientStats.down < clientStats.total:
return "#f37b24"; // Orange
default:
return "#cf3c3c"; // Red
}
}
function userExpiryColor(threshold, client, isDark = false) {
if (!client.enable) {
return isDark ? '#2c3950' : '#bcbcbc';
}
now = new Date().getTime(),
expiry = client.expiryTime;
switch (true) {
case expiry === null:
return "#7a316f"; // purple
case expiry < 0:
return "#008771"; // Green
case expiry == 0:
return "#7a316f"; // purple
case now < expiry - threshold:
return "#008771"; // Green
case now < expiry:
return "#f37b24"; // Orange
default:
return "#cf3c3c"; // Red
}
}
function doAllItemsExist(array1, array2) {
for (let i = 0; i < array1.length; i++) {
if (!array2.includes(array1[i])) {
return false;
}
}
return true;
}
function buildURL({ host, port, isTLS, base, path }) {
if (!host || host.length === 0) host = window.location.hostname;
if (!port || port.length === 0) port = window.location.port;
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
const protocol = isTLS ? "https:" : "http:";
port = String(port);
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
port = "";
} else {
port = `:${port}`;
}
return `${protocol}//${host}${port}${base}${path}`;
}

View File

@@ -108,14 +108,14 @@ Date.prototype.setMaxTime = function () {
* Formatting date * Formatting date
*/ */
Date.prototype.formatDate = function () { Date.prototype.formatDate = function () {
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate()); return this.getFullYear() + "-" + NumberFormatter.addZero(this.getMonth() + 1) + "-" + NumberFormatter.addZero(this.getDate());
}; };
/** /**
* Format time * Format time
*/ */
Date.prototype.formatTime = function () { Date.prototype.formatTime = function () {
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds()); return NumberFormatter.addZero(this.getHours()) + ":" + NumberFormatter.addZero(this.getMinutes()) + ":" + NumberFormatter.addZero(this.getSeconds());
}; };
/** /**

View File

@@ -80,66 +80,68 @@ class PromiseUtil {
} }
} }
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
class RandomUtil { class RandomUtil {
static randomIntRange(min, max) { static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
return Math.floor(Math.random() * (max - min) + min); let seq = '';
}
switch (type) {
static randomInt(n) { case "hex":
return this.randomIntRange(0, n); seq += "0123456789abcdef";
} break;
default:
static randomSeq(count) { if (hasNumbers) seq += "0123456789";
let str = ''; if (hasLowercase) seq += "abcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < count; ++i) { if (hasUppercase) seq += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
str += seq[this.randomInt(62)]; break;
} }
return str;
return seq;
}
static randomInteger(min, max) {
const range = max - min + 1;
const randomBuffer = new Uint32Array(1);
window.crypto.getRandomValues(randomBuffer);
return Math.floor((randomBuffer[0] / (0xFFFFFFFF + 1)) * range) + min;
}
static randomSeq(count, options = {}) {
const seq = this.getSeq(options);
const seqLength = seq.length;
const randomValues = new Uint32Array(count);
window.crypto.getRandomValues(randomValues);
return Array.from(randomValues, v => seq[v % seqLength]).join('');
} }
static randomShortIds() { static randomShortIds() {
const lengths = [2, 4, 6, 8, 10, 12, 14, 16]; const lengths = [2, 4, 6, 8, 10, 12, 14, 16].sort(() => Math.random() - 0.5);
for (let i = lengths.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[lengths[i], lengths[j]] = [lengths[j], lengths[i]];
}
let shortIds = []; return lengths.map(len => this.randomSeq(len, { type: "hex" })).join(',');
for (let length of lengths) {
let shortId = '';
for (let i = 0; i < length; i++) {
shortId += seq[this.randomInt(16)];
}
shortIds.push(shortId);
}
return shortIds.join(',');
} }
static randomLowerAndNum(len) { static randomLowerAndNum(len) {
let str = ''; return this.randomSeq(len, { hasUppercase: false });
for (let i = 0; i < len; ++i) {
str += seq[this.randomInt(36)];
}
return str;
} }
static randomUUID() { static randomUUID() {
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; if (window.location.protocol === "https:") {
return template.replace(/[xy]/g, function (c) { return window.crypto.randomUUID();
const randomValues = new Uint8Array(1); } else {
crypto.getRandomValues(randomValues); return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
let randomValue = randomValues[0] % 16; .replace(/[xy]/g, function (c) {
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8); const randomValues = new Uint8Array(1);
return calculatedValue.toString(16); window.crypto.getRandomValues(randomValues);
}); let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
}
} }
static randomShadowsocksPassword() { static randomShadowsocksPassword() {
let array = new Uint8Array(32); const array = new Uint8Array(32);
window.crypto.getRandomValues(array); window.crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, array)); return Base64.encode(String.fromCharCode(...array));
} }
} }
@@ -478,4 +480,304 @@ class Wireguard {
privateKey: secretKey.length > 0 ? secretKey : this.keyToBase64(privateKey) privateKey: secretKey.length > 0 ? secretKey : this.keyToBase64(privateKey)
}; };
} }
}
class ClipboardManager {
static copyText(content = "") {
// !! here old way of copying is used because not everyone can afford https connection
return new Promise((resolve) => {
try {
const textarea = window.document.createElement('textarea');
textarea.style.fontSize = '12pt';
textarea.style.border = '0';
textarea.style.padding = '0';
textarea.style.margin = '0';
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
textarea.style.top = `${window.pageYOffset || document.documentElement.scrollTop}px`;
textarea.setAttribute('readonly', '');
textarea.value = content;
window.document.body.appendChild(textarea);
textarea.select();
window.document.execCommand("copy");
window.document.body.removeChild(textarea);
resolve(true)
} catch {
resolve(false)
}
})
}
}
class Base64 {
static encode(content = "", safe = false) {
if (safe) {
return Base64.encode(content)
.replace(/\+/g, '-')
.replace(/=/g, '')
.replace(/\//g, '_')
}
return window.btoa(
String.fromCharCode(...new TextEncoder().encode(content))
)
}
static decode(content = "") {
return new TextDecoder()
.decode(
Uint8Array.from(window.atob(content), c => c.charCodeAt(0))
)
}
}
class SizeFormatter {
static ONE_KB = 1024;
static ONE_MB = this.ONE_KB * 1024;
static ONE_GB = this.ONE_MB * 1024;
static ONE_TB = this.ONE_GB * 1024;
static ONE_PB = this.ONE_TB * 1024;
static sizeFormat(size) {
if (size <= 0) return "0 B";
if (size < this.ONE_KB) return size.toFixed(0) + " B";
if (size < this.ONE_MB) return (size / this.ONE_KB).toFixed(2) + " KB";
if (size < this.ONE_GB) return (size / this.ONE_MB).toFixed(2) + " MB";
if (size < this.ONE_TB) return (size / this.ONE_GB).toFixed(2) + " GB";
if (size < this.ONE_PB) return (size / this.ONE_TB).toFixed(2) + " TB";
return (size / this.ONE_PB).toFixed(2) + " PB";
}
}
class CPUFormatter {
static cpuSpeedFormat(speed) {
return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz";
}
static cpuCoreFormat(cores) {
return cores === 1 ? "1 Core" : cores + " Cores";
}
}
class TimeFormatter {
static formatSecond(second) {
if (second < 60) return second.toFixed(0) + 's';
if (second < 3600) return (second / 60).toFixed(0) + 'm';
if (second < 3600 * 24) return (second / 3600).toFixed(0) + 'h';
let day = Math.floor(second / 3600 / 24);
let remain = ((second / 3600) - (day * 24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
class NumberFormatter {
static addZero(num) {
return num < 10 ? "0" + num : num;
}
static toFixed(num, n) {
n = Math.pow(10, n);
return Math.floor(num * n) / n;
}
}
class Utils {
static debounce(fn, delay) {
let timeoutID = null;
return function () {
clearTimeout(timeoutID);
let args = arguments;
let that = this;
timeoutID = setTimeout(() => fn.apply(that, args), delay);
};
}
}
class CookieManager {
static getCookie(cname) {
let name = cname + '=';
let ca = document.cookie.split(';');
for (let c of ca) {
c = c.trim();
if (c.indexOf(name) === 0) {
return decodeURIComponent(c.substring(name.length, c.length));
}
}
return '';
}
static 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 + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
}
}
class ColorUtils {
static usageColor(data, threshold, total) {
switch (true) {
case data === null: return "purple";
case total < 0: return "green";
case total == 0: return "purple";
case data < total - threshold: return "green";
case data < total: return "orange";
default: return "red";
}
}
static clientUsageColor(clientStats, trafficDiff) {
switch (true) {
case !clientStats || clientStats.total == 0: return "#7a316f";
case clientStats.up + clientStats.down < clientStats.total - trafficDiff: return "#008771";
case clientStats.up + clientStats.down < clientStats.total: return "#f37b24";
default: return "#cf3c3c";
}
}
static userExpiryColor(threshold, client, isDark = false) {
if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc';
let now = new Date().getTime(), expiry = client.expiryTime;
switch (true) {
case expiry === null: return "#7a316f";
case expiry < 0: return "#008771";
case expiry == 0: return "#7a316f";
case now < expiry - threshold: return "#008771";
case now < expiry: return "#f37b24";
default: return "#cf3c3c";
}
}
}
class ArrayUtils {
static doAllItemsExist(array1, array2) {
return array1.every(item => array2.includes(item));
}
}
class URLBuilder {
static buildURL({ host, port, isTLS, base, path }) {
if (!host || host.length === 0) host = window.location.hostname;
if (!port || port.length === 0) port = window.location.port;
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
const protocol = isTLS ? "https:" : "http:";
port = String(port);
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
port = "";
} else {
port = `:${port}`;
}
return `${protocol}//${host}${port}${base}${path}`;
}
}
class LanguageManager {
static supportedLanguages = [
{
name: "English",
value: "en-US",
icon: "🇺🇸",
},
{
name: "فارسی",
value: "fa-IR",
icon: "🇮🇷",
},
{
name: "简体中文",
value: "zh-CN",
icon: "🇨🇳",
},
{
name: "繁體中文",
value: "zh-TW",
icon: "🇹🇼",
},
{
name: "日本語",
value: "ja-JP",
icon: "🇯🇵",
},
{
name: "Русский",
value: "ru-RU",
icon: "🇷🇺",
},
{
name: "Tiếng Việt",
value: "vi-VN",
icon: "🇻🇳",
},
{
name: "Español",
value: "es-ES",
icon: "🇪🇸",
},
{
name: "Indonesian",
value: "id-ID",
icon: "🇮🇩",
},
{
name: "Український",
value: "uk-UA",
icon: "🇺🇦",
},
{
name: "Türkçe",
value: "tr-TR",
icon: "🇹🇷",
},
{
name: "Português",
value: "pt-BR",
icon: "🇧🇷",
}
]
static getLanguage() {
let lang = CookieManager.getCookie("lang");
if (!lang) {
if (window.navigator) {
lang = window.navigator.language || window.navigator.userLanguage;
if (LanguageManager.isSupportLanguage(lang)) {
CookieManager.setCookie("lang", lang, 150);
} else {
CookieManager.setCookie("lang", "en-US", 150);
window.location.reload();
}
} else {
CookieManager.setCookie("lang", "en-US", 150);
window.location.reload();
}
}
return lang;
}
static setLanguage(language) {
if (!LanguageManager.isSupportLanguage(language)) {
language = "en-US";
}
CookieManager.setCookie("lang", language, 150);
window.location.reload();
}
static isSupportLanguage(language) {
const languageFilter = LanguageManager.supportedLanguages.filter((lang) => {
return lang.value === language
})
return languageFilter.length > 0;
}
} }

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ import (
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -49,8 +50,8 @@ func (a *IndexController) index(c *gin.Context) {
func (a *IndexController) login(c *gin.Context) { func (a *IndexController) login(c *gin.Context) {
var form LoginForm var form LoginForm
err := c.ShouldBind(&form)
if err != nil { if err := c.ShouldBind(&form); err != nil {
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
return return
} }
@@ -68,29 +69,31 @@ func (a *IndexController) login(c *gin.Context) {
safeUser := template.HTMLEscapeString(form.Username) safeUser := template.HTMLEscapeString(form.Username)
safePass := template.HTMLEscapeString(form.Password) safePass := template.HTMLEscapeString(form.Password)
safeSecret := template.HTMLEscapeString(form.LoginSecret) safeSecret := template.HTMLEscapeString(form.LoginSecret)
if user == nil { if user == nil {
logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c)) logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c))
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0) a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return return
} else {
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
} }
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
sessionMaxAge, err := a.settingService.GetSessionMaxAge() sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil { if err != nil {
logger.Warning("Unable to get session's max age from DB") logger.Warning("Unable to get session's max age from DB")
} }
err = session.SetMaxAge(c, sessionMaxAge*60) session.SetMaxAge(c, sessionMaxAge*60)
if err != nil { session.SetLoginUser(c, user)
logger.Warning("Unable to set session's max age") if err := sessions.Default(c).Save(); err != nil {
logger.Warning("Unable to save session: ", err)
return
} }
err = session.SetLoginUser(c, user) logger.Infof("%s logged in successfully", safeUser)
logger.Infof("%s logged in successfully", user.Username) jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil)
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
} }
func (a *IndexController) logout(c *gin.Context) { func (a *IndexController) logout(c *gin.Context) {
@@ -99,6 +102,9 @@ func (a *IndexController) logout(c *gin.Context) {
logger.Infof("%s logged out successfully", user.Username) logger.Infof("%s logged out successfully", user.Username)
} }
session.ClearSession(c) session.ClearSession(c)
if err := sessions.Default(c).Save(); err != nil {
logger.Warning("Unable to save session after clearing:", err)
}
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
} }

View File

@@ -16,47 +16,49 @@ type Msg struct {
} }
type AllSetting struct { type AllSetting struct {
WebListen string `json:"webListen" form:"webListen"` WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"` WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"` WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"` WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"` WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"` SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
PageSize int `json:"pageSize" form:"pageSize"` PageSize int `json:"pageSize" form:"pageSize"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"` ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"` TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
RemarkModel string `json:"remarkModel" form:"remarkModel"` RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"` TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"` SecretEnable bool `json:"secretEnable" form:"secretEnable"`
SubEnable bool `json:"subEnable" form:"subEnable"` SubEnable bool `json:"subEnable" form:"subEnable"`
SubListen string `json:"subListen" form:"subListen"` SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"` SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"` SubPath string `json:"subPath" form:"subPath"`
SubDomain string `json:"subDomain" form:"subDomain"` SubDomain string `json:"subDomain" form:"subDomain"`
SubCertFile string `json:"subCertFile" form:"subCertFile"` SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubUpdates int `json:"subUpdates" form:"subUpdates"` SubUpdates int `json:"subUpdates" form:"subUpdates"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`
SubURI string `json:"subURI" form:"subURI"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` SubURI string `json:"subURI" form:"subURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
Datepicker string `json:"datepicker" form:"datepicker"` SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
Datepicker string `json:"datepicker" form:"datepicker"`
} }
func (s *AllSetting) CheckValid() error { func (s *AllSetting) CheckValid() error {

View File

@@ -5,10 +5,8 @@
<script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/qs/qs.min.js"></script> <script src="{{ .base_path }}assets/qs/qs.min.js"></script>
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/langs.js"></script>
<script> <script>
const basePath = '{{ .base_path }}'; const basePath = '{{ .base_path }}';
axios.defaults.baseURL = basePath; axios.defaults.baseURL = basePath;

View File

@@ -10,7 +10,7 @@
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag> <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
<tr-qr-bg class="qr-bg-sub"> <tr-qr-bg class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner"> <tr-qr-bg-inner class="qr-bg-sub-inner">
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas> <canvas @click="copy(genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas>
</tr-qr-bg-inner> </tr-qr-bg-inner>
</tr-qr-bg> </tr-qr-bg>
</tr-qr-box> </tr-qr-box>
@@ -18,7 +18,7 @@
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag> <a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
<tr-qr-bg class="qr-bg-sub"> <tr-qr-bg class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner"> <tr-qr-bg-inner class="qr-bg-sub-inner">
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas> <canvas @click="copy(genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas>
</tr-qr-bg-inner> </tr-qr-bg-inner>
</tr-qr-bg> </tr-qr-bg>
</tr-qr-box> </tr-qr-box>
@@ -27,7 +27,7 @@
<tr-qr-box class="qr-box"> <tr-qr-box class="qr-box">
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag> <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
<tr-qr-bg class="qr-bg"> <tr-qr-bg class="qr-bg">
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas> <canvas @click="copy(row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
</tr-qr-bg> </tr-qr-bg>
</tr-qr-box> </tr-qr-box>
</template> </template>
@@ -41,7 +41,6 @@
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
client: null, client: null,
qrcodes: [], qrcodes: [],
clipboard: null,
visible: false, visible: false,
subId: '', subId: '',
show: function(title = '', dbInbound, client) { show: function(title = '', dbInbound, client) {
@@ -79,14 +78,12 @@
qrModal: qrModal, qrModal: qrModal,
}, },
methods: { methods: {
copyToClipboard(elementId, content) { copy(content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, { ClipboardManager
text: () => content, .copyText(content)
}); .then(() => {
this.qrModal.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') })
this.qrModal.clipboard.destroy();
});
}, },
setQrCode(elementId, content) { setQrCode(elementId, content) {
new QRious({ new QRious({

View File

@@ -7,7 +7,7 @@
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName">[[ txtModal.fileName ]] :download="txtModal.fileName">[[ txtModal.fileName ]]
</a-button> </a-button>
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button> <a-button type="primary" @click="txtModal.copy(txtModal.content)">{{ i18n "copy" }}</a-button>
</template> </template>
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content" <a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
:autosize="{ minRows: 10, maxRows: 20}"></a-input> :autosize="{ minRows: 10, maxRows: 20}"></a-input>
@@ -20,24 +20,20 @@
content: '', content: '',
fileName: '', fileName: '',
qrcode: null, qrcode: null,
clipboard: null,
visible: false, visible: false,
show: function (title = '', content = '', fileName = '') { show: function (title = '', content = '', fileName = '') {
this.title = title; this.title = title;
this.content = content; this.content = content;
this.fileName = fileName; this.fileName = fileName;
this.visible = true; this.visible = true;
textModalApp.$nextTick(() => { },
if (this.clipboard === null) { copy: function (content = '') {
this.clipboard = new ClipboardJS('#copy-btn', { ClipboardManager
text: () => this.content, .copyText(content)
}); .then(() => {
this.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') this.close();
this.close(); })
});
}
});
}, },
close: function () { close: function () {
this.visible = false; this.visible = false;

View File

@@ -422,16 +422,16 @@
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<password-input autocomplete="password" name="password" icon="lock" v-model.trim="user.password" <a-password-input autocomplete="password" name="password" icon="lock" v-model.trim="user.password"
placeholder='{{ i18n "password" }}' placeholder='{{ i18n "password" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </a-password-input>
</a-form-item> </a-form-item>
<a-form-item v-if="secretEnable"> <a-form-item v-if="secretEnable">
<password-input autocomplete="secret" name="secret" icon="key" v-model.trim="user.loginSecret" <a-password-input autocomplete="secret" name="secret" icon="key" v-model.trim="user.loginSecret"
placeholder='{{ i18n "secretToken" }}' placeholder='{{ i18n "secretToken" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </a-password-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
@@ -449,9 +449,9 @@
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<a-col :span="24"> <a-col :span="24">
<a-select ref="selectLang" v-model="lang" <a-select ref="selectLang" v-model="lang"
@change="setLang(lang)" style="width: 200px;" @change="LanguageManager.setLanguage(lang)" style="width: 200px;"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l.value" label="English" v-for="l in supportLangs"> <a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages">
<span role="img" aria-label="l.name" v-text="l.icon"></span> <span role="img" aria-label="l.name" v-text="l.icon"></span>
&nbsp;&nbsp;<span v-text="l.name"></span> &nbsp;&nbsp;<span v-text="l.name"></span>
</a-select-option> </a-select-option>
@@ -461,7 +461,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<theme-switch-login></theme-switch-login> <a-theme-switch-login></a-theme-switch-login>
</a-row> </a-row>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -493,7 +493,7 @@
lang: "" lang: ""
}, },
async created() { async created() {
this.lang = getLang(); this.lang = LanguageManager.getLanguage();
this.secretEnable = await this.getSecretStatus(); this.secretEnable = await this.getSecretStatus();
}, },
methods: { methods: {

View File

@@ -14,10 +14,10 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1"> <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>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1"> <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>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0"> <a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input> <a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
@@ -26,7 +26,7 @@
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input> <a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2"> <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>
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS"> <a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -61,7 +61,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number style="width: 50%" v-model="clientsBulkModal.tgId" min="0"></a-input-number> <a-input-number style="width: 50%" v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="app.ipLimitEnable"> <a-form-item v-if="app.ipLimitEnable">
<template slot="label"> <template slot="label">
@@ -73,7 +73,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number> <a-input-number v-model.number="clientsBulkModal.limitIp" min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
@@ -85,7 +85,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> <a-input-number v-model.number="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'> <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch> <a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
@@ -106,9 +106,9 @@
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime"></a-date-picker> v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"> value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime">
</persian-datepicker> </a-persian-datepicker>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label"> <template slot="label">

View File

@@ -34,7 +34,7 @@
{{define "commonSider"}} {{define "commonSider"}}
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md"> <a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md">
<theme-switch></theme-switch> <a-theme-switch></a-theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>
@@ -43,7 +43,7 @@
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</div> </div>
<theme-switch></theme-switch> <a-theme-switch></a-theme-switch>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>

View File

@@ -1,26 +1,46 @@
{{define "component/passwordInput"}} {{define "component/passwordInput"}}
<template> <template>
<a-input :value="value" :type="showPassword ? 'text' : 'password'" <a-input :value="value" :type="showPassword ? 'text' : 'password'" :placeholder="placeholder"
:placeholder="placeholder" :autocomplete="autocomplete" :name="name" @input="$emit('input', $event.target.value)">
:autocomplete="autocomplete" <template v-if="icon" #prefix>
:name="name" <a-icon :type="icon" style="font-size: 16px;" />
@input="$emit('input', $event.target.value)"> </template>
<template v-if="icon" #prefix> <template #addonAfter>
<a-icon :type="icon" style="font-size: 16px;" /> <a-icon :type="showPassword ? 'eye-invisible' : 'eye'" @click="toggleShowPassword" style="font-size: 16px;" />
</template> </template>
<template #addonAfter> </a-input>
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
@click="toggleShowPassword"
style="font-size: 16px;" />
</template>
</a-input>
</template> </template>
{{end}} {{end}}
{{define "component/password"}} {{define "component/password"}}
<script> <script>
Vue.component('password-input', { Vue.component('a-password-input', {
props: ["title", "value", "placeholder", "icon", "autocomplete", "name"], props: {
'title': {
type: String,
required: false,
},
'value': {
type: String,
required: false,
},
'placeholder': {
type: String,
required: false,
},
'autocomplete': {
type: String,
required: false,
},
'name': {
type: String,
required: false,
},
'icon': {
type: undefined,
required: false
}
},
template: `{{template "component/passwordInput"}}`, template: `{{template "component/passwordInput"}}`,
data() { data() {
return { return {

View File

@@ -2,10 +2,10 @@
<template> <template>
<div> <div>
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker" <a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();" @input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder"> :placeholder="placeholder">
<template #addonAfter> <template #addonAfter>
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/> <a-icon type="calendar" style="font-size: 14px; opacity: 0.5;" />
</template> </template>
</a-input> </a-input>
</div> </div>
@@ -13,15 +13,27 @@
{{end}} {{end}}
{{define "component/persianDatepicker"}} {{define "component/persianDatepicker"}}
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}"/> <link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
<script> <script>
const persianDatepicker = {}; const persianDatepicker = {};
Vue.component('persian-datepicker', { Vue.component('a-persian-datepicker', {
props: ['placeholder', 'format', 'value'], props: {
'format': {
type: undefined,
required: false,
},
'value': {
type: String,
required: false,
},
'placeholder': {
type: String,
required: false,
},
},
template: `{{template "component/persianDatepickerTemplate"}}`, template: `{{template "component/persianDatepickerTemplate"}}`,
data() { data() {
return { return {
@@ -57,4 +69,4 @@
} }
}); });
</script> </script>
{{end}} {{end}}

View File

@@ -1,26 +1,18 @@
{{define "component/settingListItem"}} {{define "component/settingListItem"}}
<a-list-item style="padding: 20px"> <a-list-item :style="{ padding: padding }">
<a-row v-if="type === 'textarea'"> <a-row>
<a-col>
<a-list-item-meta :title="title" :description="desc"/>
<a-textarea class="ant-setting-textarea" :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10 }"></a-textarea>
<!--a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 30 }"></a-textarea-->
</a-col>
</a-row>
<a-row v-else>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta :title="title" :description="desc"/> <a-list-item-meta>
<template #title>
<slot name="title"></slot>
</template>
<template #description>
<slot name="description"></slot>
</template>
</a-list-item-meta>
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template v-if="type === 'text'"> <slot name="control"></slot>
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
</template>
<template v-else-if="type === 'number'">
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" :max="max" style="width: 100%;"></a-input-number>
</template>
<template v-else-if="type === 'switch'">
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
</template>
</a-col> </a-col>
</a-row> </a-row>
</a-list-item> </a-list-item>
@@ -28,9 +20,38 @@
{{define "component/setting"}} {{define "component/setting"}}
<script> <script>
Vue.component('setting-list-item', { Vue.component('a-setting-list-item', {
props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"], props: {
template: `{{template "component/settingListItem"}}`, 'title': {
}); type: String,
required: true,
},
'description': {
type: String,
required: false,
},
'paddings': {
type: String,
required: false,
defaultValue: "default",
validator: function (value) {
return ['small', 'default'].includes(value)
}
}
},
template: `{{ template "component/settingListItem" }}`,
computed: {
padding() {
switch (this.paddings) {
case "small":
return "10px 20px !important"
break;
case "default":
return "20px !important"
break;
}
}
}
})
</script> </script>
{{end}} {{end}}

View File

@@ -1,9 +1,5 @@
{{define "component/sortableTableTrigger"}} {{define "component/sortableTableTrigger"}}
<a-icon type="drag" <a-icon type="drag" class="sortable-icon" style="cursor: move;" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
class="sortable-icon"
style="cursor: move;"
@mouseup="mouseUpHandler"
@mousedown="mouseDownHandler"
@click="clickHandler" /> @click="clickHandler" />
{{end}} {{end}}
@@ -28,7 +24,16 @@
newElementIndex: null, newElementIndex: null,
}; };
}, },
props: ['data-source', 'customRow'], props: {
'data-source': {
type: undefined,
required: false,
},
'customRow': {
type: undefined,
required: false,
}
},
inheritAttrs: false, inheritAttrs: false,
provide() { provide() {
const sortable = {} const sortable = {}
@@ -44,7 +49,7 @@
sortable, sortable,
} }
}, },
render: function(createElement) { render: function (createElement) {
return createElement('a-table', { return createElement('a-table', {
class: { class: {
'ant-table-is-sorting': this.isDragging(), 'ant-table-is-sorting': this.isDragging(),
@@ -59,7 +64,7 @@
drop: (e) => this.dropHandler(e), drop: (e) => this.dropHandler(e),
}, },
scopedSlots: this.$scopedSlots, scopedSlots: this.$scopedSlots,
}, this.$slots.default, ) }, this.$slots.default,)
}, },
created() { created() {
this.$memoSort = {}; this.$memoSort = {};
@@ -163,9 +168,14 @@
} }
} }
}); });
Vue.component('table-sort-trigger', { Vue.component('a-table-sort-trigger', {
template: `{{template "component/sortableTableTrigger"}}`, template: `{{template "component/sortableTableTrigger"}}`,
props: ['item-index'], props: {
'item-index': {
type: undefined,
required: false
}
},
inject: ['sortable'], inject: ['sortable'],
methods: { methods: {
mouseDownHandler(e) { mouseDownHandler(e) {
@@ -190,27 +200,33 @@
display: none; display: none;
} }
} }
.ant-table-is-sorting .draggable-row td { .ant-table-is-sorting .draggable-row td {
background-color: #ffffff !important; background-color: #ffffff !important;
} }
.dark .ant-table-is-sorting .draggable-row td { .dark .ant-table-is-sorting .draggable-row td {
background-color: var(--dark-color-surface-100) !important; background-color: var(--dark-color-surface-100) !important;
} }
.ant-table-is-sorting .dragging td { .ant-table-is-sorting .dragging td {
background-color: rgb(232 244 242) !important; background-color: rgb(232 244 242) !important;
color: rgba(0, 0, 0, 0.3); color: rgba(0, 0, 0, 0.3);
} }
.dark .ant-table-is-sorting .dragging td { .dark .ant-table-is-sorting .dragging td {
background-color: var(--dark-color-table-hover) !important; background-color: var(--dark-color-table-hover) !important;
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
} }
.ant-table-is-sorting .dragging { .ant-table-is-sorting .dragging {
opacity: 1; opacity: 1;
box-shadow: 1px -2px 2px #008771; box-shadow: 1px -2px 2px #008771;
transition: all 0.2s; transition: all 0.2s;
} }
.ant-table-is-sorting .dragging .ant-table-row-index { .ant-table-is-sorting .dragging .ant-table-row-index {
opacity: 0.3; opacity: 0.3;
} }
</style> </style>
{{end}} {{end}}

View File

@@ -6,9 +6,13 @@
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<span>Theme</span> <span>Theme</span>
</span> </span>
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark <a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch> <a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark
<a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme"
@change="themeSwitcher.toggleTheme()"></a-switch>
</a-menu-item> </a-menu-item>
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox> <a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
@mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;"
:checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
</a-menu-item> </a-menu-item>
</a-sub-menu> </a-sub-menu>
</a-menu> </a-menu>
@@ -17,12 +21,15 @@
{{define "component/themeSwitchTemplateLogin"}} {{define "component/themeSwitchTemplateLogin"}}
<template> <template>
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> <a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline"
selected-keys="">
<a-menu-item mode="inline" class="ant-menu-theme-switch"> <a-menu-item mode="inline" class="ant-menu-theme-switch">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch> <a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
@change="themeSwitcher.toggleTheme()"></a-switch>
<template v-if="themeSwitcher.isDarkTheme"> <template v-if="themeSwitcher.isDarkTheme">
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()">Ultra</a-checkbox> <a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra"
@click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
</template> </template>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -83,8 +90,7 @@
}; };
} }
const themeSwitcher = createThemeSwitcher(); const themeSwitcher = createThemeSwitcher();
Vue.component('theme-switch', { Vue.component('a-theme-switch', {
props: [],
template: `{{template "component/themeSwitchTemplate"}}`, template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({ data: () => ({
themeSwitcher themeSwitcher
@@ -96,8 +102,7 @@
document.getElementById('message').className = themeSwitcher.currentTheme; document.getElementById('message').className = themeSwitcher.currentTheme;
} }
}); });
Vue.component('theme-switch-login', { Vue.component('a-theme-switch-login', {
props: [],
template: `{{template "component/themeSwitchTemplateLogin"}}`, template: `{{template "component/themeSwitchTemplateLogin"}}`,
data: () => ({ data: () => ({
themeSwitcher themeSwitcher
@@ -110,4 +115,4 @@
} }
}); });
</script> </script>
{{end}} {{end}}

View File

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

View File

@@ -66,7 +66,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number style="width: 50%" v-model="client.tgId" min="0"></a-input-number> <a-input-number style="width: 50%" v-model.number="client.tgId" min="0"></a-input-number>
</a-form-item>
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
<a-input v-model.trim="client.comment"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="app.ipLimitEnable"> <a-form-item v-if="app.ipLimitEnable">
<template slot="label"> <template slot="label">
@@ -78,7 +81,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model="client.limitIp" min="0"></a-input-number> <a-input-number v-model.number="client.limitIp" min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit"> <a-form-item v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
<template slot="label"> <template slot="label">
@@ -120,13 +123,13 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <a-input-number v-model.number="client._totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'> <a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)"> <a-tag :color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] / [[ SizeFormatter.sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]] [[ SizeFormatter.sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]]) ([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag> </a-tag>
<a-tooltip> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
@@ -151,8 +154,8 @@
</template> </template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker> :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker> value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>
<a-form-item v-if="client.expiryTime != 0"> <a-form-item v-if="client.expiryTime != 0">

View File

@@ -41,7 +41,7 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> <a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
@@ -57,9 +57,9 @@
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" <a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> v-model="dbInbound._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"> value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
</persian-datepicker> </a-persian-datepicker>
</a-form-item> </a-form-item>
</a-form> </a-form>

View File

@@ -66,7 +66,7 @@
</a-divider> </a-divider>
<a-form-item label='Type'> <a-form-item label='Type'>
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['rand','base64','str']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['rand','base64','str', 'hex']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Packet'> <a-form-item label='Packet'>
@@ -377,6 +377,30 @@
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </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> </template>
</template> </template>
@@ -441,17 +465,22 @@
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="TCP Fast Open"> <a-form-item label='Address Port Strategy'>
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch> <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>
<a-form-item label="Keep Alive Interval"> <a-form-item label="Keep Alive Interval">
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number> <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>
<a-form-item label="Multipath TCP"> <a-form-item label="Multipath TCP">
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch> <a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="TCP No-Delay" v-if="outbound.stream.sockopt.tcpMptcp"> <a-form-item label="Penetrate">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch> <a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
@@ -462,10 +491,10 @@
</a-form-item> </a-form-item>
<template v-if="outbound.mux.enabled"> <template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency"> <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>
<a-form-item label="xudp Concurrency"> <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>
<a-form-item label="xudp UDP 443"> <a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">

View File

@@ -18,7 +18,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
@@ -42,7 +42,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='xVer'> <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-item>
</a-form> </a-form>
<a-divider style="margin:5px 0;"></a-divider> <a-divider style="margin:5px 0;"></a-divider>

View File

@@ -18,7 +18,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality"> <template v-if="inbound.isTcp">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
@@ -42,7 +42,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='xVer'> <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-item>
</a-form> </a-form>
<a-divider style="margin:5px 0;"></a-divider> <a-divider style="margin:5px 0;"></a-divider>

View File

@@ -7,7 +7,7 @@
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='uTLS'> <a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%" <a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
@@ -43,11 +43,11 @@
<a-form-item label='SpiderX'> <a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input> <a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input> <a-input v-model="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="password" v-model="inbound.stream.reality.privateKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button> <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>

View File

@@ -10,7 +10,7 @@
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input> <a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')"></a-button> <a-button icon="plus" size="small" @click="inbound.stream.httpupgrade.addHeader('', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">

View File

@@ -8,6 +8,7 @@
<a-select-option value="wechat-video">WeChat</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="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View File

@@ -6,22 +6,22 @@
</a-form-item> </a-form-item>
<template v-if="inbound.stream.sockoptSwitch"> <template v-if="inbound.stream.sockoptSwitch">
<a-form-item label="Route Mark"> <a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP Keep Alive Interval"> <a-form-item label="TCP Keep Alive Interval">
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP Keep Alive Idle"> <a-form-item label="TCP Keep Alive Idle">
<a-input-number v-model="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP Max Seg"> <a-form-item label="TCP Max Seg">
<a-input-number v-model="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP User Timeout"> <a-form-item label="TCP User Timeout">
<a-input-number v-model="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="TCP Window Clamp"> <a-form-item label="TCP Window Clamp">
<a-input-number v-model="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Proxy Protocol"> <a-form-item label="Proxy Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
@@ -32,8 +32,8 @@
<a-form-item label="Multipath TCP"> <a-form-item label="Multipath TCP">
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="TCP No-Delay" v-if="inbound.stream.sockopt.tcpMptcp"> <a-form-item label="Penetrate">
<a-switch v-model.trim="inbound.stream.sockopt.tcpNoDelay"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="V6 Only"> <a-form-item label="V6 Only">
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>

View File

@@ -7,7 +7,7 @@
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input> <a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('host', '')"></a-button> <a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
@@ -17,7 +17,7 @@
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" <a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)">-</a-button> <a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
@@ -27,14 +27,14 @@
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Max Buffered Upload"> <a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
<a-input v-model.trim="inbound.stream.xhttp.scMaxBufferedPosts"></a-input> <a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="Max Upload Size (Byte)"> <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-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Min Upload Interval (Ms)"> <a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
<a-input v-model.trim="inbound.stream.xhttp.scMinPostsIntervalMs"></a-input> <a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Padding Bytes"> <a-form-item label="Padding Bytes">
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input> <a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
@@ -42,26 +42,5 @@
<a-form-item label="No SSE Header"> <a-form-item label="No SSE Header">
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch> <a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Max Concurrency" v-if="!inbound.stream.xhttp.xmux.maxConnections">
<a-input v-model="inbound.stream.xhttp.xmux.maxConcurrency"></a-input>
</a-form-item>
<a-form-item label="Max Connections" v-if="!inbound.stream.xhttp.xmux.maxConcurrency">
<a-input v-model="inbound.stream.xhttp.xmux.maxConnections"></a-input>
</a-form-item>
<a-form-item label="Max Reuse Times">
<a-input v-model="inbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
</a-form-item>
<a-form-item label="Max Lifetime (ms)">
<a-input v-model="inbound.stream.xhttp.xmux.cMaxLifetimeMs"></a-input>
</a-form-item>
<a-form-item label="Max Request Times">
<a-input v-model="inbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
</a-form-item>
<a-form-item label='Keep Alive Period'>
<a-input v-model.number="inbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input>
</a-form-item>
<a-form-item label="No gRPC Header">
<a-switch v-model="inbound.stream.xhttp.noGRPCHeader"></a-switch>
</a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -34,7 +34,7 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%" <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option> <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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
@@ -57,6 +57,9 @@
<a-form-item label="Session Resumption"> <a-form-item label="Session Resumption">
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch> <a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="VerifyPeerCertInNames">
<a-input v-model.trim="inbound.stream.tls.verifyPeerCertInNames"></a-input>
</a-form-item>
<template v-for="cert,index in inbound.stream.tls.certs"> <template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'> <a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid"> <a-radio-group v-model="cert.useFile" button-style="solid">
@@ -82,10 +85,10 @@
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> <a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input> <a-input v-model="cert.cert"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'> <a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input> <a-input type="password" v-model="cert.key"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<a-form-item label='OCSP stapling'> <a-form-item label='OCSP stapling'>

View File

@@ -55,18 +55,18 @@
<template slot="content" v-if="client.email"> <template slot="content" v-if="client.email">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr> </tr>
<tr v-if="client.totalGB > 0"> <tr v-if="client.totalGB > 0">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<table> <table>
<tr class="tr-table-box"> <tr class="tr-table-box">
<td class="tr-table-rt"> [[ sizeFormat(getSumStats(record, client.email)) ]] </td> <td class="tr-table-rt"> [[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]] </td>
<td class="tr-table-bar" v-if="!client.enable"> <td class="tr-table-bar" v-if="!client.enable">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" /> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td> </td>
@@ -124,9 +124,9 @@
</template> </template>
</span> </span>
</template> </template>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag> <a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</a-popover> </a-popover>
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag"> <a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path> <path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
</svg> </svg>
@@ -172,7 +172,7 @@
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td> <td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
</tr> </tr>
<tr> <tr>
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td> <td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
<td width="120px" v-if="!client.enable"> <td width="120px" v-if="!client.enable">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" /> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td> </td>
@@ -181,12 +181,12 @@
<template slot="content" v-if="client.email"> <template slot="content" v-if="client.email">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
@@ -244,7 +244,7 @@
</template> </template>
</span> </span>
</template> </template>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag> <a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</a-popover> </a-popover>
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag"> <a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">

View File

@@ -181,8 +181,35 @@
<tr v-if="infoModal.clientStats"> <tr v-if="infoModal.clientStats">
<td>{{ i18n "usage" }}</td> <td>{{ i18n "usage" }}</td>
<td> <td>
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag> <a-tag color="green">[[ SizeFormatter.sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag> <a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up) ]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
</td>
</tr>
<tr v-if="infoModal.clientSettings.comment">
<td>{{ i18n "comment" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.comment ]]">
<a-tag class="info-large-tag">[[ infoModal.clientSettings.comment ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr v-if="app.ipLimitEnable">
<td>{{ i18n "pages.inbounds.IPLimit" }}</td>
<td>
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
</td>
</tr>
<tr v-if="app.ipLimitEnable">
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
<td>
<a-tag>[[ infoModal.clientIps ]]</a-tag>
<a-icon type="sync" :spin="refreshing" @click="refreshIPs" style="margin: 0 5px;"></a-icon>
<a-tooltip :title="[[ dbInbound.address ]]">
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</template>
<a-icon type="delete" @click="clearClientIps"></a-icon>
</a-tooltip>
</td> </td>
</tr> </tr>
</table> </table>
@@ -197,7 +224,7 @@
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
</td> </td>
<td> <td>
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag> <a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ SizeFormatter.sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag>
<a-tag v-else color="purple" class="infinite-tag"> <a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path> <path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
@@ -206,7 +233,7 @@
</td> </td>
<td> <td>
<template v-if="infoModal.clientSettings.expiryTime > 0"> <template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> <a-tag :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
<template v-if="app.datepicker === 'gregorian'"> <template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</template> </template>
@@ -231,7 +258,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="purple">Subscription Link</a-tag> <a-tag color="purple">Subscription Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-button> <a-button size="small" icon="snippets" @click="copy(infoModal.subLink)"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a> <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
@@ -240,7 +267,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag> <a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"></a-button> <a-button size="small" icon="snippets" @click="copy(infoModal.subJsonLink)"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a> <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
@@ -252,7 +279,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag> <a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button size="small" icon="snippets" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"></a-button> <a-button size="small" icon="snippets" @click="copy(infoModal.clientSettings.tgId)"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
</tr-info-row> </tr-info-row>
@@ -263,7 +290,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button> <a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
<code>[[ link.link ]]</code> <code>[[ link.link ]]</code>
@@ -277,7 +304,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button> <a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
<code>[[ link.link ]]</code> <code>[[ link.link ]]</code>
@@ -400,11 +427,11 @@
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<tr-info-row v-for="(link,index) in infoModal.links" class="tr-info-row"> <tr-info-row class="tr-info-row">
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="blue">Config</a-tag> <a-tag color="blue">Config</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])"></a-button> <a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(infoModal.links[index])"></a-button>
</a-tooltip> </a-tooltip>
</tr-info-title> </tr-info-title>
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row"> <div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row">
@@ -417,6 +444,18 @@
</template> </template>
</a-modal> </a-modal>
<script> <script>
function refreshIPs(email) {
return HttpUtil.post(`/panel/inbound/clientIps/${email}`).then((msg) => {
if (msg.success) {
try {
return JSON.parse(msg.obj).join(', ');
} catch (e) {
return msg.obj;
}
}
});
}
const infoModal = { const infoModal = {
visible: false, visible: false,
inbound: new Inbound(), inbound: new Inbound(),
@@ -425,12 +464,12 @@
clientStats: [], clientStats: [],
upStats: 0, upStats: 0,
downStats: 0, downStats: 0,
clipboard: null,
links: [], links: [],
index: null, index: null,
isExpired: false, isExpired: false,
subLink: '', subLink: '',
subJsonLink: '', subJsonLink: '',
clientIps: '',
show(dbInbound, index) { show(dbInbound, index) {
this.index = index; this.index = index;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
@@ -438,6 +477,12 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null; this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry; this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index) : this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : []; this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
if (app.ipLimitEnable && this.clientSettings.limitIp) {
refreshIPs(this.clientStats.email).then((ips) => {
this.clientIps = ips;
})
}
if (this.inbound.protocol == Protocols.WIREGUARD) { if (this.inbound.protocol == Protocols.WIREGUARD) {
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n') this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else { } else {
@@ -466,6 +511,7 @@
el: '#inbound-info-modal', el: '#inbound-info-modal',
data: { data: {
infoModal, infoModal,
refreshing: false,
get dbInbound() { get dbInbound() {
return this.infoModal.dbInbound; return this.infoModal.dbInbound;
}, },
@@ -486,21 +532,39 @@
}, },
}, },
methods: { methods: {
copyToClipboard(elementId, content) { copy(content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, { ClipboardManager
text: () => content, .copyText(content)
}); .then(() => {
this.infoModal.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') })
this.infoModal.clipboard.destroy();
});
}, },
statsColor(stats) { statsColor(stats) {
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total); return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
}, },
getRemStats() { getRemStats() {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down; remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
return remained > 0 ? sizeFormat(remained) : '-'; return remained > 0 ? SizeFormatter.sizeFormat(remained) : '-';
},
refreshIPs() {
this.refreshing = true;
refreshIPs(this.infoModal.clientStats.email)
.then((ips) => {
this.infoModal.clientIps = ips;
})
.finally(() => {
this.refreshing = false;
});
},
clearClientIps() {
HttpUtil.post(`/panel/inbound/clearClientIps/${this.infoModal.clientStats.email}`)
.then((msg) => {
if (!msg.success) {
return;
}
this.infoModal.clientIps = 'No IP Record';
})
.catch(() => {});
}, },
}, },
}); });

View File

@@ -141,11 +141,11 @@
<a-row> <a-row>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.totalDownUp" }}: {{ i18n "pages.inbounds.totalDownUp" }}:
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag> <a-tag color="green">[[ SizeFormatter.sizeFormat(total.up) ]] / [[ SizeFormatter.sizeFormat(total.down) ]]</a-tag>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.totalUsage" }}: {{ i18n "pages.inbounds.totalUsage" }}:
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag> <a-tag color="green">[[ SizeFormatter.sizeFormat(total.up + total.down) ]]</a-tag>
</a-col> </a-col>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.inboundCount" }}: {{ i18n "pages.inbounds.inboundCount" }}:
@@ -375,19 +375,19 @@
<template slot="content"> <template slot="content">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr> </tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total"> <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] / [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -408,7 +408,7 @@
<template v-else slot="content"> <template v-else slot="content">
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]] [[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
</template> </template>
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)"> <a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ remainedDays(dbInbound._expiryTime) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
</a-popover> </a-popover>
@@ -474,19 +474,19 @@
<template slot="content"> <template slot="content">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr> </tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total"> <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] / [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -544,9 +544,7 @@
</a-layout> </a-layout>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></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/uri/URI.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/inbound.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> <script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
@@ -885,7 +883,7 @@
this.exportSubs(dbInbound.id); this.exportSubs(dbInbound.id);
break; break;
case "clipboard": case "clipboard":
this.copyToClipboard(dbInbound.id); this.copy(dbInbound.id);
break; break;
case "resetTraffic": case "resetTraffic":
this.resetTraffic(dbInbound.id); this.resetTraffic(dbInbound.id);
@@ -929,7 +927,7 @@
expiryTime: dbInbound.expiryTime, expiryTime: dbInbound.expiryTime,
listen: '', listen: '',
port: RandomUtil.randomIntRange(10000, 60000), port: RandomUtil.randomInteger(10000, 60000),
protocol: baseInbound.protocol, protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(), settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(), streamSettings: baseInbound.stream.toString(),
@@ -1108,6 +1106,36 @@
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`); this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
} }
}, },
getSubGroupClients(dbInbounds, currentClient) {
const response = {
inbounds: [],
clients: [],
editIds: []
}
if (dbInbounds && dbInbounds.length > 0 && currentClient) {
dbInbounds.forEach((dbInboundItem) => {
const dbInbound = new DBInbound(dbInboundItem);
if (dbInbound) {
const inbound = dbInbound.toInbound();
if (inbound) {
const clients = inbound.clients;
if (clients.length > 0) {
clients.forEach((client) => {
if (client['subId'] === currentClient['subId']) {
client['inboundId'] = dbInboundItem.id
client['clientId'] = this.getClientId(dbInbound.protocol, client)
response.inbounds.push(dbInboundItem.id)
response.clients.push(client)
response.editIds.push(client['clientId'])
}
})
}
}
}
})
}
return response;
},
getClientId(protocol, client) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
case Protocols.TROJAN: return client.password; case Protocols.TROJAN: return client.password;
@@ -1246,9 +1274,9 @@
return remained>0 ? remained : 0; return remained>0 ? remained : 0;
}, },
clientStatsColor(dbInbound, email) { clientStatsColor(dbInbound, email) {
if (email.length == 0) return clientUsageColor(); if (email.length == 0) return ColorUtils.clientUsageColor();
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
return clientUsageColor(clientStats, app.trafficDiff) return ColorUtils.clientUsageColor(clientStats, app.trafficDiff)
}, },
statsProgress(dbInbound, email) { statsProgress(dbInbound, email) {
if (email.length == 0) return 100; if (email.length == 0) return 100;
@@ -1266,17 +1294,17 @@
}, },
remainedDays(expTime){ remainedDays(expTime){
if (expTime == 0) return null; if (expTime == 0) return null;
if (expTime < 0) return formatSecond(expTime/-1000); if (expTime < 0) return TimeFormatter.formatSecond(expTime/-1000);
now = new Date().getTime(); now = new Date().getTime();
if (expTime < now) return '{{ i18n "depleted" }}'; if (expTime < now) return '{{ i18n "depleted" }}';
return formatSecond((expTime-now)/1000); return TimeFormatter.formatSecond((expTime-now)/1000);
}, },
statsExpColor(dbInbound, email){ statsExpColor(dbInbound, email){
if (email.length == 0) return '#7a316f'; if (email.length == 0) return '#7a316f';
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
if (!clientStats) return '#7a316f'; if (!clientStats) return '#7a316f';
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total); statsColor = ColorUtils.usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime); expColor = ColorUtils.usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
switch (true) { switch (true) {
case statsColor == "red" || expColor == "red": case statsColor == "red" || expColor == "red":
return "#cf3c3c"; // Red return "#cf3c3c"; // Red
@@ -1310,7 +1338,7 @@
if (clients != null){ if (clients != null){
clients.forEach(c => { clients.forEach(c => {
if (c.subId && c.subId.length>0){ if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId) subLinks.push(this.subSettings.subURI + c.subId)
} }
}) })
} }
@@ -1337,7 +1365,7 @@
if (clients != null){ if (clients != null){
clients.forEach(c => { clients.forEach(c => {
if (c.subId && c.subId.length>0){ if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId) subLinks.push(this.subSettings.subURI + c.subId)
} }
}) })
} }
@@ -1354,9 +1382,9 @@
} }
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds'); txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
}, },
copyToClipboard(dbInboundId) { copy(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2)); txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
}, },
async startDataRefreshLoop() { async startDataRefreshLoop() {
while (this.isRefreshEnabled) { while (this.isRefreshEnabled) {
@@ -1410,7 +1438,7 @@
} }
}, },
watch: { watch: {
searchKey: debounce(function (newVal) { searchKey: Utils.debounce(function (newVal) {
this.searchInbounds(newVal); this.searchInbounds(newVal);
}, 500) }, 500)
}, },

View File

@@ -19,6 +19,18 @@
.ant-card-dark h2 { .ant-card-dark h2 {
color: var(--dark-color-text-primary); color: var(--dark-color-text-primary);
} }
.ant-backup-list-item {
gap: 10px;
user-select: none;
cursor: pointer;
}
.dark .ant-backup-list-item svg {
color: var(--dark-color-text-primary);
}
.dark .ant-backup-list,
.dark .ant-xray-version-list {
border-color: var(--dark-color-stroke);
}
</style> </style>
<body> <body>
@@ -45,11 +57,11 @@
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color" :stroke-color="status.cpu.color"
:percent="status.cpu.percent"></a-progress> :percent="status.cpu.percent"></a-progress>
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]] <a-tooltip> <div><b>CPU:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
<a-icon type="area-chart"></a-icon> <a-icon type="area-chart"></a-icon>
<template slot="title"> <template slot="title">
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div> <div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div>
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div> <div><b>Speed:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
</template> </template>
</a-tooltip></div> </a-tooltip></div>
</a-col> </a-col>
@@ -58,7 +70,7 @@
:stroke-color="status.mem.color" :stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress> :percent="status.mem.percent"></a-progress>
<div> <div>
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] <b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -70,7 +82,7 @@
:stroke-color="status.swap.color" :stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress> :percent="status.swap.percent"></a-progress>
<div> <div>
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] <b>Swap:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
</div> </div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
@@ -78,7 +90,7 @@
:stroke-color="status.disk.color" :stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress> :percent="status.disk.percent"></a-progress>
<div> <div>
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] <b>{{ i18n "pages.index.hard"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -99,8 +111,8 @@
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<b>{{ i18n "pages.index.operationHours" }}:</b> <b>{{ i18n "pages.index.operationHours" }}:</b>
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag> <a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag> <a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
@@ -145,7 +157,7 @@
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<b>{{ i18n "usage"}}:</b> <b>{{ i18n "usage"}}:</b>
<a-tag color="green"> RAM: [[ sizeFormat(status.appStats.mem) ]] </a-tag> <a-tag color="green"> RAM: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag> <a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
</a-card> </a-card>
</a-col> </a-col>
@@ -207,7 +219,7 @@
<a-col :span="12"> <a-col :span="12">
<a-tag> <a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="arrow-up"></a-icon> Up: [[ sizeFormat(status.netIO.up) ]]/s <a-icon type="arrow-up"></a-icon> Up: [[ SizeFormatter.sizeFormat(status.netIO.up) ]]/s
<template slot="title"> <template slot="title">
{{ i18n "pages.index.upSpeed" }} {{ i18n "pages.index.upSpeed" }}
</template> </template>
@@ -217,7 +229,7 @@
<a-col :span="12"> <a-col :span="12">
<a-tag> <a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="arrow-down"></a-icon> Down: [[ sizeFormat(status.netIO.down) ]]/s <a-icon type="arrow-down"></a-icon> Down: [[ SizeFormatter.sizeFormat(status.netIO.down) ]]/s
<template slot="title"> <template slot="title">
{{ i18n "pages.index.downSpeed" }} {{ i18n "pages.index.downSpeed" }}
</template> </template>
@@ -236,7 +248,7 @@
<a-icon type="cloud-upload"></a-icon> <a-icon type="cloud-upload"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalSent" }} {{ i18n "pages.index.totalSent" }}
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]] </template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
</a-tooltip> </a-tooltip>
</a-tag> </a-tag>
</a-col> </a-col>
@@ -246,7 +258,7 @@
<a-icon type="cloud-download"></a-icon> <a-icon type="cloud-download"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalReceive" }} {{ i18n "pages.index.totalReceive" }}
</template> In: [[ sizeFormat(status.netTraffic.recv) ]] </template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
</a-tooltip> </a-tooltip>
</a-tag> </a-tag>
</a-col> </a-col>
@@ -260,14 +272,16 @@
</a-layout> </a-layout>
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true" <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer=""> @ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content" <a-alert type="warning" style="margin-bottom: 12px; width: 100%;"
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert> message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
<template v-for="version, index in versionModal.versions"> <a-list class="ant-xray-version-list" bordered style="width: 100%;">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px" <a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
@click="switchV2rayVersion(version)"> <a-list-item-meta>
[[ version ]] <template #title>[[ version ]]</template>
</a-tag> </a-list-item-meta>
</template> <a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
</a-list-item>
</a-list>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" <a-modal id="log-modal" v-model="logModal.visible"
:closable="true" @cancel="() => logModal.visible = false" :closable="true" @cancel="() => logModal.visible = false"
@@ -314,25 +328,34 @@
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div>
</a-modal> </a-modal>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal"
:closable="true" footer="" v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
:closable="true"
footer=""
:class="themeSwitcher.currentTheme"> :class="themeSwitcher.currentTheme">
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content" <a-list class="ant-backup-list" bordered style="width: 100%;">
:message="backupModal.description" <a-list-item class="ant-backup-list-item" @click="exportDatabase()">
show-icon> <a-list-item-meta>
</a-alert> <template #title>{{ i18n "pages.index.exportDatabase" }}</template>
<a-space direction="horizontal" style="text-align: center; margin-bottom: 10px;"> <template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
<a-button type="primary" @click="exportDatabase()"> </a-list-item-meta>
[[ backupModal.exportText ]] <a-icon type="right" />
</a-button> </a-list-item>
<a-button type="primary" @click="importDatabase()"> <a-list-item class="ant-backup-list-item" @click="importDatabase()">
[[ backupModal.importText ]] <a-list-item-meta>
</a-button> <template #title>{{ i18n "pages.index.importDatabase" }}</template>
</a-space> <template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
<templaet #avatar>
<a-icon type="import" />
</templaet>
</a-list-item-meta>
<a-icon type="right" />
</a-list-item>
</a-list>
</a-modal> </a-modal>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}} {{template "component/themeSwitcher" .}}
{{template "textModal"}} {{template "textModal"}}
<script> <script>
@@ -354,7 +377,7 @@
if (this.total === 0) { if (this.total === 0) {
return 0; return 0;
} }
return toFixed(this.current / this.total * 100, 2); return NumberFormatter.toFixed(this.current / this.total * 100, 2);
} }
get color() { get color() {
@@ -397,7 +420,7 @@
this.logicalPro = data.logicalPro; this.logicalPro = data.logicalPro;
this.cpuSpeedMhz = data.cpuSpeedMhz; this.cpuSpeedMhz = data.cpuSpeedMhz;
this.disk = new CurTotal(data.disk.current, data.disk.total); this.disk = new CurTotal(data.disk.current, data.disk.total);
this.loads = data.loads.map(load => toFixed(load, 2)); this.loads = data.loads.map(load => NumberFormatter.toFixed(load, 2));
this.mem = new CurTotal(data.mem.current, data.mem.total); this.mem = new CurTotal(data.mem.current, data.mem.total);
this.netIO = data.netIO; this.netIO = data.netIO;
this.netTraffic = data.netTraffic; this.netTraffic = data.netTraffic;
@@ -492,24 +515,11 @@
const backupModal = { const backupModal = {
visible: false, visible: false,
title: '', show() {
description: '', this.visible = true;
exportText: '',
importText: '',
show({
title = '{{ i18n "pages.index.backupTitle" }}',
description = '{{ i18n "pages.index.backupDescription" }}',
exportText = '{{ i18n "pages.index.exportDatabase" }}',
importText = '{{ i18n "pages.index.importDatabase" }}',
}) {
this.title = title;
this.description = description;
this.exportText = exportText;
this.importText = importText;
this.visible = true;
}, },
hide() { hide() {
this.visible = false; this.visible = false;
}, },
}; };
@@ -605,12 +615,7 @@
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json'); txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
}, },
openBackup() { openBackup() {
backupModal.show({ backupModal.show();
title: '{{ i18n "pages.index.backupTitle" }}',
description: '{{ i18n "pages.index.backupDescription" }}',
exportText: '{{ i18n "pages.index.exportDatabase" }}',
importText: '{{ i18n "pages.index.importDatabase" }}',
});
}, },
exportDatabase() { exportDatabase() {
window.location = basePath + 'server/getDb'; window.location = basePath + 'server/getDb';

View File

@@ -108,16 +108,17 @@
</a-row> </a-row>
</a-card> </a-card>
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'> <a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<a-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<a-row style="padding: 20px"> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>
<a-list-item-meta title='{{ i18n "pages.settings.remarkModel"}}'> {{ i18n "pages.settings.remarkModel"}}
<template slot="description">{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i></template> </template>
</a-list-item-meta> <template #description>
</a-col> {{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i>
<a-col :lg="24" :xl="12"> </template>
<template #control>
<a-input-group style="width: 100%;"> <a-input-group style="width: 100%;">
<a-select style="padding-right: .5rem; min-width: 80%; width: auto;" <a-select style="padding-right: .5rem; min-width: 80%; width: auto;"
mode="multiple" mode="multiple"
@@ -129,271 +130,523 @@
<a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-input-group> </a-input-group>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item> <template #title>{{ i18n "pages.settings.panelListeningIP"}}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item> <template #description>{{ i18n "pages.settings.panelListeningIPDesc"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="1" :max="65531"></setting-list-item> <template #control>
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item> <a-input type="text" v-model="allSetting.webListen"></a-input>
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item> </template>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item> </a-setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="60"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="number" title='{{ i18n "pages.settings.pageSize" }}' desc='{{ i18n "pages.settings.pageSizeDesc" }}' v-model="allSetting.pageSize" :min="0" :step="5"></setting-list-item> <template #title>{{ i18n "pages.settings.panelListeningDomain"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item> <template #description>{{ i18n "pages.settings.panelListeningDomainDesc"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item> <template #control>
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item> <a-input type="text" v-model="allSetting.webDomain"></a-input>
<a-list-item> </template>
<a-row style="padding: 20px"> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<a-list-item-meta title='{{ i18n "pages.settings.datepicker"}}'> <template #title>{{ i18n "pages.settings.panelPort"}}</template>
<template slot="description">{{ i18n "pages.settings.datepickerDescription"}}</template> <template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
</a-list-item-meta> <template #control>
</a-col> <a-input-number :min="1" :min="65531" v-model="allSetting.webPort" style="width: 100%;"></a-input>
<a-col :lg="24" :xl="12"> </template>
<template> </a-setting-list-item>
<a-select style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker"> <a-setting-list-item paddings="small">
<a-select-option v-for="item in datepickerList" :value="item.value"> <template #title>{{ i18n "pages.settings.panelUrlPath"}}</template>
<span v-text="item.name"></span> <template #description>{{ i18n "pages.settings.panelUrlPathDesc"}}</template>
</a-select-option> <template #control>
</a-select> <a-input type="text" v-model="allSetting.webBasePath"></a-input>
</template> </template>
</a-col> </a-setting-list-item>
</a-row> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.settings.sessionMaxAge" }}</template>
<a-list-item> <template #description>{{ i18n "pages.settings.sessionMaxAgeDesc" }}</template>
<a-row style="padding: 20px"> <template #control>
<a-col :lg="24" :xl="12"> <a-input-number :min="60" v-model="allSetting.sessionMaxAge" style="width: 100%;"></a-input>
<a-list-item-meta title="Language"></a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<template> <template #title>{{ i18n "pages.settings.pageSize" }}</template>
<a-select ref="selectLang" <template #description>{{ i18n "pages.settings.pageSizeDesc" }}</template>
v-model="lang" <template #control>
@change="setLang(lang)" <a-input-number :min="0" step="5" v-model="allSetting.pageSize" style="width: 100%;"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme" </template>
style="width: 100%"> </a-setting-list-item>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <a-setting-list-item paddings="small">
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span> <template #title>{{ i18n "pages.settings.language"}}</template>
</a-select-option> <template #control>
</a-select> <a-select ref="selectLang"
</template> v-model="lang"
</a-col> @change="LanguageManager.setLanguage(lang)"
</a-row> :dropdown-class-name="themeSwitcher.currentTheme"
</a-list-item> style="width: 100%">
</a-list> <a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span>
</a-select-option>
</a-select>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.notifications" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.expireTimeDiff" }}</template>
<template #description>{{ i18n "pages.settings.expireTimeDiffDesc" }}</template>
<template #control>
<a-input-number :min="0" v-model="allSetting.expireDiff" style="width: 100%;"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.trafficDiff" }}</template>
<template #description>{{ i18n "pages.settings.trafficDiffDesc" }}</template>
<template #control>
<a-input-number :min="0" v-model="allSetting.trafficDiff" style="width: 100%;"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.certs" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.publicKeyPath"}}</template>
<template #description>{{ i18n "pages.settings.publicKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.webCertFile"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.privateKeyPath"}}</template>
<template #description>{{ i18n "pages.settings.privateKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.webKeyFile"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.externalTraffic" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.externalTrafficInformEnable"}}</template>
<template #description>{{ i18n "pages.settings.externalTrafficInformEnableDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.externalTrafficInformEnable"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.externalTrafficInformURI"}}</template>
<template #description>{{ i18n "pages.settings.externalTrafficInformURIDesc"}}</template>
<template #control>
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.externalTrafficInformURI"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.dateAndTime" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.timeZone"}}</template>
<template #description>{{ i18n "pages.settings.timeZoneDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.timeLocation"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.datepicker"}}</template>
<template #description>{{ i18n "pages.settings.datepickerDescription"}}</template>
<template #control>
<a-select style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker">
<a-select-option v-for="item in datepickerList" :value="item.value">
<span v-text="item.name"></span>
</a-select-option>
</a-select>
</template>
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;"> <a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' style="padding-top: 20px;">
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider> <a-collapse>
<a-form layout="horizontal" :colon="false" style="float: left; margin-bottom: 2rem;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }"> <a-collapse-panel header='{{ i18n "pages.settings.security.admin"}}'>
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'> <a-setting-list-item paddings="small">
<a-input autocomplete="username" v-model="user.oldUsername"></a-input> <template #title>{{ i18n "pages.settings.oldUsername"}}</template>
</a-form-item> <template #control>
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'> <a-input autocomplete="username" v-model="user.oldUsername"></a-input>
<password-input autocomplete="current-password" v-model="user.oldPassword"></password-input> </template>
</a-form-item> </a-setting-list-item>
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'> <a-setting-list-item paddings="small">
<a-input v-model="user.newUsername"></a-input> <template #title>{{ i18n "pages.settings.currentPassword"}}</template>
</a-form-item> <template #control>
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'> <a-password-input autocomplete="current-password" v-model="user.oldPassword"></a-password-input>
<password-input autocomplete="new-password" v-model="user.newPassword"></password-input> </template>
</a-form-item> </a-setting-list-item>
<a-form-item label=" "> <a-setting-list-item paddings="small">
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button> <template #title>{{ i18n "pages.settings.newUsername"}}</template>
</a-form-item> <template #control>
</a-form> <a-input v-model="user.newUsername"></a-input>
<a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider> </template>
<a-form style="padding: 0 20px;"> </a-setting-list-item>
<a-list-item> <a-setting-list-item paddings="small">
<a-row> <template #title>{{ i18n "pages.settings.newPassword"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title='{{ i18n "pages.settings.security.loginSecurity" }}' <a-password-input autocomplete="new-password" v-model="user.newPassword"></a-password-input>
description='{{ i18n "pages.settings.security.loginSecurityDesc" }}'> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-list-item>
<a-col :lg="24" :xl="12"> <a-space direction="horizontal" style="padding: 0 20px;">
<template> <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch> </a-space>
<a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon> </a-list-item>
</template> </a-collapse-panel>
</a-col> <a-collapse-panel header='{{ i18n "pages.settings.security.secret"}}'>
</a-row> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
<a-list-item> <template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
<a-row> <template #control>
<a-col :lg="24" :xl="12"> <a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
<a-list-item-meta title='{{ i18n "pages.settings.security.secretToken" }}' <a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon>
description='{{ i18n "pages.settings.security.secretTokenDesc" }}'> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
<template> <template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea> <template #control>
</template> <a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-list-item>
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button> <a-space direction="horizontal" style="padding: 0 20px;">
</a-form> <a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
</a-space>
</a-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item> <template #title>{{ i18n "pages.settings.telegramBotEnable" }}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item> <template #description>{{ i18n "pages.settings.telegramBotEnableDesc" }}</template>
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item> <template #control>
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item> <a-switch v-model="allSetting.tgBotEnable"></a-switch>
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item> </template>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramProxy"}}' desc='{{ i18n "pages.settings.telegramProxyDesc"}}' v-model="allSetting.tgBotProxy" placeholder="socks5://user:pass@host:port"></setting-list-item> </a-setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramAPIServer"}}' desc='{{ i18n "pages.settings.telegramAPIServerDesc"}}' v-model="allSetting.tgBotAPIServer" placeholder="https://api.example.com"></setting-list-item> <a-setting-list-item paddings="small">
<a-list-item> <template #title>{{ i18n "pages.settings.telegramToken"}}</template>
<a-row style="padding: 20px"> <template #description>{{ i18n "pages.settings.telegramTokenDesc"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title="Telegram Bot Language" /> <a-input type="text" v-model="allSetting.tgBotToken"></a-input>
</a-col> </template>
<a-col :lg="24" :xl="12"> </a-setting-list-item>
<template> <a-setting-list-item paddings="small">
<a-select ref="selectBotLang" v-model="allSetting.tgLang" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> <template #title>{{ i18n "pages.settings.telegramChatId"}}</template>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <template #description>{{ i18n "pages.settings.telegramChatIdDesc"}}</template>
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span> <template #control>
</a-select-option> <a-input type="text" v-model="allSetting.tgBotChatId"></a-input>
</a-select> </template>
</template> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
</a-row> <template #title>{{ i18n "pages.settings.telegramBotLanguage"}}</template>
</a-list-item> <template #control>
</a-list> <a-select ref="selectBotLang" v-model="allSetting.tgLang" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span>
</a-select-option>
</a-select>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.notifications" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.telegramNotifyTime"}}</template>
<template #description>{{ i18n "pages.settings.telegramNotifyTimeDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.tgRunTime"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.tgNotifyBackup" }}</template>
<template #description>{{ i18n "pages.settings.tgNotifyBackupDesc" }}</template>
<template #control>
<a-switch v-model="allSetting.tgBotBackup"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.tgNotifyLogin" }}</template>
<template #description>{{ i18n "pages.settings.tgNotifyLoginDesc" }}</template>
<template #control>
<a-switch v-model="allSetting.tgBotLoginNotify"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.tgNotifyCpu" }}</template>
<template #description>{{ i18n "pages.settings.tgNotifyCpuDesc" }}</template>
<template #control>
<a-input-number :min="0" :min="100" v-model="allSetting.tgCpu"></a-switch>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.proxyAndServer" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.telegramProxy"}}</template>
<template #description>{{ i18n "pages.settings.telegramProxyDesc"}}</template>
<template #control>
<a-input type="text" placeholder="socks5://user:pass@host:port" v-model="allSetting.tgBotProxy"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.telegramAPIServer"}}</template>
<template #description>{{ i18n "pages.settings.telegramAPIServerDesc"}}</template>
<template #control>
<a-input type="text" placeholder="https://api.example.com" v-model="allSetting.tgBotAPIServer"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'> <a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item> <template #title>{{ i18n "pages.settings.subEnable"}}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item> <template #description>{{ i18n "pages.settings.subEnableDesc"}}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item> <template #control>
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort" :min="1" :max="65531"></setting-list-item> <a-switch v-model="allSetting.subEnable"></a-switch>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item> </template>
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item> </a-setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item> <template #title>{{ i18n "pages.settings.subListen"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item> <template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
</a-list> <template #control>
<a-input type="text" v-model="allSetting.subListen"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subDomain"}}</template>
<template #description>{{ i18n "pages.settings.subDomainDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subDomain"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subPort"}}</template>
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
<template #control>
<a-input-number v-model="allSetting.subPort" :min="1" :min="65531" style="width: 100%;"></a-input-number>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subPath"}}</template>
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subPath"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subURI"}}</template>
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
<template #control>
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.subURI"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.information" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subEncrypt"}}</template>
<template #description>{{ i18n "pages.settings.subEncryptDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subEncrypt"></a-switch>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subShowInfo"}}</template>
<template #description>{{ i18n "pages.settings.subShowInfoDesc"}}</template>
<template #control>
<a-switch v-model="allSetting.subShowInfo"></a-switch>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.certs" }}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subCertPath"}}</template>
<template #description>{{ i18n "pages.settings.subCertPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subCertFile"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subKeyPath"}}</template>
<template #description>{{ i18n "pages.settings.subKeyPathDesc"}}</template>
<template #control>
<a-input type="text" v-model="allSetting.subKeyFile"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.intervals"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subUpdates"}}</template>
<template #description>{{ i18n "pages.settings.subUpdatesDesc"}}</template>
<template #control>
<a-input-number :min="1" v-model="allSetting.subUpdates" style="width: 100%;"></a-input-number>
</template>
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable"> <a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item> <a-setting-list-item paddings="small">
<a-list-item style="padding: 20px"> <template #title>{{ i18n "pages.settings.subPath"}}</template>
<a-row> <template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title='{{ i18n "pages.settings.fragment"}}'> <a-input type="text" v-model="allSetting.subJsonPath"></a-input>
<template slot="description">{{ i18n "pages.settings.fragmentDesc"}}</template> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.settings.subURI"}}</template>
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
<template #control>
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.subJsonURI"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.fragment"}}</template>
<template #description>{{ i18n "pages.settings.fragmentDesc"}}</template>
<template #control>
<a-switch v-model="fragment"></a-switch> <a-switch v-model="fragment"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="fragment" style="margin-top: 14px;"> <a-list-item v-if="fragment" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment"> <a-collapse>
<setting-list-item style="padding: 10px 20px" type="text" title='Packets' v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></setting-list-item> <a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment">
<setting-list-item style="padding: 10px 20px" type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item style="padding: 10px 20px" type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item> <template #title>Packets</template>
</a-collapse-panel> <template #control>
</a-collapse> <a-input type="text" v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></a-input>
</a-list-item> </template>
<a-list-item style="padding: 20px"> </a-setting-list-item>
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>Length</template>
<a-list-item-meta title='Noises'> <template #control>
<template slot="description">{{ i18n "pages.settings.noisesDesc"}}</template> <a-input type="text" v-model="fragmentLength" placeholder="100-200"></a-input>
</a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<template #title>Interval</template>
<template #control>
<a-input type="text" v-model="fragmentInterval" placeholder="10-20"></a-input>
</template>
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header="Noises">
<a-setting-list-item paddings="small">
<template #title>Noises</template>
<template #description>{{ i18n "pages.settings.noisesDesc"}}</template>
<template #control>
<a-switch v-model="noises"></a-switch> <a-switch v-model="noises"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="noises" style="margin-top: 14px;"> <a-list-item v-if="noises" style="padding: 10px 20px;">
<a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise ${index + 1}`"> <a-collapse>
<a-list-item style="padding: 10px 20px"> <a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise №${index + 1}`">
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>Type</template>
<a-list-item-meta title='Type'></a-list-item-meta> <template #control>
</a-col>
<a-col :lg="24" :xl="12">
<a-select :value="noise.type" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" <a-select :value="noise.type" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme"
@change="(value) => updateNoiseType(index, value)"> @change="(value) => updateNoiseType(index, value)">
<a-select-option :value="p" :label="p" v-for="p in ['rand', 'base64', 'str']" :key="p"> <a-select-option :value="p" :label="p" v-for="p in ['rand', 'base64', 'str', 'hex']" :key="p">
[[ p ]] </a-select-option> [[ p ]] </a-select-option>
</a-select> </a-select>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-setting-list-item paddings="small">
<setting-list-item style="padding: 10px 20px" type="text" title='Packet' :value="noise.packet" <template #title>Packet</template>
@input="(value) => updateNoisePacket(index, value)" placeholder="5-10"></setting-list-item> <template #control>
<setting-list-item style="padding: 10px 20px" type="text" title='Delay (ms)' :value="noise.delay" <a-input type="text" :value="noise.packet" @input="(value) => updateNoisePacket(index, event.target.value)" placeholder="5-10"></a-input>
@input="(value) => updateNoiseDelay(index, value)" placeholder="10-20"></setting-list-item> </template>
<a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button> </a-setting-list-item>
</a-collapse-panel> <a-setting-list-item paddings="small">
</a-collapse> <template #title>Delay (ms)</template>
<a-button v-if="noises" type="primary" @click="addNoise" style="margin-top: 10px">Add Noise</a-button> <template #control>
</a-list-item> <a-input type="text" :value="noise.delay" @input="(value) => updateNoiseDelay(index, event.target.value)" placeholder="10-20"></a-input>
<a-list-item style="padding: 20px"> </template>
<a-row> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-space direction="horizontal" style="padding: 10px 20px;">
<a-list-item-meta title='{{ i18n "pages.settings.mux"}}'> <a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button>
<template slot="description">{{ i18n "pages.settings.muxDesc"}}</template> </a-space>
</a-list-item-meta> </a-collapse-panel>
</a-col> </a-collapse>
<a-col :lg="24" :xl="12"> <a-button v-if="noises" type="primary" @click="addNoise" style="margin-top: 10px">Add Noise</a-button>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.settings.mux"}}'>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.mux"}}</template>
<template #description>{{ i18n "pages.settings.muxDesc"}}</template>
<template #control>
<a-switch v-model="enableMux"></a-switch> <a-switch v-model="enableMux"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="enableMux" style="margin-top: 14px;"> <a-list-item v-if="enableMux" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'> <a-collapse>
<setting-list-item style="padding: 10px 20px" type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'>
<setting-list-item style="padding: 10px 20px" type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item> <a-setting-list-item paddings="small">
<a-list-item style="padding: 10px 20px"> <template #title>Concurrency</template>
<a-row> <template #control>
<a-col :lg="24" :xl="12"> <a-input-number v-model="muxConcurrency" :min="-1" :max="1024" style="width: 100%;"></a-input-number>
<a-list-item-meta title='xudp UDP 443'></a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<template #title>xudp Concurrency</template>
<template #control>
<a-input-number v-model="muxXudpConcurrency" :min="-1" :max="1024" style="width: 100%;"></a-input-number>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>xudp UDP 443</template>
<template #control>
<a-select v-model="muxXudpProxyUDP443" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="muxXudpProxyUDP443" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']"> [[ p ]] </a-select-option> <a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']"> [[ p ]] </a-select-option>
</a-select> </a-select>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> </a-collapse-panel>
</a-collapse-panel> </a-collapse>
</a-collapse> </a-list-item>
</a-list-item> </a-collapse-panel>
<a-list-item style="padding: 20px"> <a-collapse-panel header='{{ i18n "pages.settings.direct" }}'>
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.settings.direct"}}</template>
<a-list-item-meta title='{{ i18n "pages.settings.direct"}}'> <template #description>{{ i18n "pages.settings.directDesc"}}</template>
<template slot="description">{{ i18n "pages.settings.directDesc"}}</template> <template #control>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<a-switch v-model="enableDirect"></a-switch> <a-switch v-model="enableDirect"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="enableDirect" style="margin-top: 14px;"> <a-list-item v-if="enableDirect" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.xray.directips"}}'> <a-collapse>
<a-list-item style="padding: 10px 20px"> <a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
<a-checkbox-group v-model="directIPs" :options="IPsOptions"></a-checkbox-group> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.xray.directips" }}</template>
</a-collapse-panel> <template #control>
<a-collapse-panel header='{{ i18n "pages.xray.directdomains"}}'> <a-select mode="tags" style="width: 100%" v-model="directIPs" :dropdown-class-name="themeSwitcher.currentTheme">
<a-list-item style="padding: 10px 20px"> <a-select-option :value="p.value" :label="p.label" v-for="p in directIPsOptions">[[ p.label ]]</a-select-option>
<a-checkbox-group v-model="directDomains" :options="DomainsOptions"></a-checkbox-group> </a-select>
</a-list-item> </template>
</a-collapse-panel> </a-setting-list-item>
</a-collapse> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.xray.directdomains" }}</template>
</a-list> <template #control>
<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 diretDomainsOptions">[[ p.label ]]</a-select-option>
</a-select>
</template>
</a-setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
@@ -419,7 +672,7 @@
allSetting: new AllSetting(), allSetting: new AllSetting(),
saveBtnDisable: true, saveBtnDisable: true,
user: {}, user: {},
lang: getLang(), lang: LanguageManager.getLanguage(),
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' }, remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'], remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }], datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
@@ -439,7 +692,7 @@
sockopt: { sockopt: {
tcpKeepAliveIdle: 100, tcpKeepAliveIdle: 100,
tcpMptcp: true, tcpMptcp: true,
tcpNoDelay: true penetrate: true
} }
} }
}, },
@@ -476,25 +729,26 @@
] ]
}, },
], ],
IPsOptions: [ directIPsOptions: [
{ label: 'Private IP', value: 'private' }, { label: 'Private IP', value: 'geoip:private' },
{ label: '🇮🇷 Iran', value: 'ir' }, { label: '🇮🇷 Iran', value: 'geoip:ir' },
{ label: '🇨🇳 China', value: 'cn' }, { label: '🇨🇳 China', value: 'geoip:cn' },
{ label: '🇷🇺 Russia', value: 'ru' }, { label: '🇷🇺 Russia', value: 'geoip:ru' },
{ label: '🇻🇳 Vietnam', value: 'vn' }, { label: '🇻🇳 Vietnam', value: 'geoip:vn' },
{ label: '🇪🇸 Spain', value: 'es' }, { label: '🇪🇸 Spain', value: 'geoip:es' },
{ label: '🇮🇩 Indonesia', value: 'id' }, { label: '🇮🇩 Indonesia', value: 'geoip:id' },
{ label: '🇺🇦 Ukraine', value: 'ua' }, { label: '🇺🇦 Ukraine', value: 'geoip:ua' },
{ label: '🇹🇷 Türkiye', value: 'tr' }, { label: '🇹🇷 Türkiye', value: 'geoip:tr' },
{ label: '🇧🇷 Brazil', value: 'br' }, { label: '🇧🇷 Brazil', value: 'geoip:br' },
], ],
DomainsOptions: [ diretDomainsOptions: [
{ label: '🇮🇷 Iran', value: 'ir' }, { label: 'Private DNS', value: 'geosite:private' },
{ label: '🇨🇳 China', value: 'cn' }, { label: '🇮🇷 Iran', value: 'geosite:category-ir' },
{ label: '🇷🇺 Russia', value: 'ru' }, { label: '🇨🇳 China', value: 'geosite:cn' },
{ label: 'Apple', value: 'apple' }, { label: '🇷🇺 Russia', value: 'geosite:category-ru' },
{ label: 'Meta', value: 'meta' }, { label: 'Apple', value: 'geosite:apple' },
{ label: 'Google', value: 'google' }, { label: 'Meta', value: 'geosite:meta' },
{ label: 'Google', value: 'geosite:google' },
], ],
get remarkModel() { get remarkModel() {
rm = this.allSetting.remarkModel; rm = this.allSetting.remarkModel;
@@ -578,7 +832,7 @@
if (host == this.oldAllSetting.webDomain) host = null; if (host == this.oldAllSetting.webDomain) host = null;
if (port == this.oldAllSetting.webPort) port = null; if (port == this.oldAllSetting.webPort) port = null;
const isTLS = webCertFile !== "" || webKeyFile !== ""; const isTLS = webCertFile !== "" || webKeyFile !== "";
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" }); const url = URLBuilder.buildURL({ host, port, isTLS, base, path: "panel/settings" });
window.location.replace(url); window.location.replace(url);
} }
}, },
@@ -752,7 +1006,7 @@
const rules = JSON.parse(this.allSetting.subJsonRules); const rules = JSON.parse(this.allSetting.subJsonRules);
if (!Array.isArray(rules)) return []; if (!Array.isArray(rules)) return [];
const ipRule = rules.find(r => r.ip); const ipRule = rules.find(r => r.ip);
return ipRule?.ip.map(d => d.replace("geoip:", "")) ?? []; return ipRule?.ip ?? [];
}, },
set: function (v) { set: function (v) {
let rules = JSON.parse(this.allSetting.subJsonRules); let rules = JSON.parse(this.allSetting.subJsonRules);
@@ -766,7 +1020,7 @@
rules[ruleIndex].ip = []; rules[ruleIndex].ip = [];
v.forEach(d => { v.forEach(d => {
rules[ruleIndex].ip.push("geoip:" + d); rules[ruleIndex].ip.push(d);
}); });
} }
this.allSetting.subJsonRules = JSON.stringify(rules); this.allSetting.subJsonRules = JSON.stringify(rules);
@@ -778,34 +1032,18 @@
const rules = JSON.parse(this.allSetting.subJsonRules); const rules = JSON.parse(this.allSetting.subJsonRules);
if (!Array.isArray(rules)) return []; if (!Array.isArray(rules)) return [];
const domainRule = rules.find(r => r.domain); const domainRule = rules.find(r => r.domain);
return domainRule?.domain.map(d => { return domainRule?.domain ?? [];
if (d.startsWith("geosite:category-")) {
return d.replace("geosite:category-", "");
}
return d.replace("geosite:", "");
})
?? [];
}, },
set: function (v) { set: function (v) {
let rules = JSON.parse(this.allSetting.subJsonRules); let rules = JSON.parse(this.allSetting.subJsonRules);
if (!Array.isArray(rules)) return; if (!Array.isArray(rules)) return;
if (v.length == 0) { if (v.length == 0) {
rules = rules.filter(r => !r.domain); rules = rules.filter(r => !r.domain);
} else { } else {
let ruleIndex = rules.findIndex(r => r.domain); let ruleIndex = rules.findIndex(r => r.domain);
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1; if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1;
rules[ruleIndex].domain = []; rules[ruleIndex].domain = v;
v.forEach(d => {
let category = '';
if (["cn", "apple", "meta", "google"].includes(d)) {
category = "";
} else if (["ru", "ir"].includes(d)) {
category = "category-";
}
rules[ruleIndex].domain.push("geosite:" + category + d);
});
} }
this.allSetting.subJsonRules = JSON.stringify(rules); this.allSetting.subJsonRules = JSON.stringify(rules);
} }
@@ -838,4 +1076,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -65,15 +65,15 @@
</tr> </tr>
<tr> <tr>
<td>WARP+ Data</td> <td>WARP+ Data</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>Quota</td> <td>Quota</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
</tr> </tr>
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)"> <tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
<td>Usage</td> <td>Usage</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
</tr> </tr>
</template> </template>
</table> </table>

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
</a-form-item> </a-form-item>
<a-form-item label='Protocol'> <a-form-item label='Protocol'>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option> <a-select-option v-for="x in ['http','tls','bittorrent','quic']" :value="x">[[ x ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Attributes'> <a-form-item label='Attributes'>

View File

@@ -106,7 +106,7 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
func (j *CheckClientIpJob) processLogFile() bool { func (j *CheckClientIpJob) processLogFile() bool {
ipRegex := regexp.MustCompile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`) ipRegex := regexp.MustCompile(`from (?:tcp:|udp:)?\[?([0-9a-fA-F\.:]+)\]?:\d+ accepted`)
emailRegex := regexp.MustCompile(`email: (.+)$`) emailRegex := regexp.MustCompile(`email: (.+)$`)
accessLogPath, _ := xray.GetAccessLogPath() accessLogPath, _ := xray.GetAccessLogPath()
@@ -151,13 +151,13 @@ func (j *CheckClientIpJob) processLogFile() bool {
} }
sort.Strings(ips) sort.Strings(ips)
inboundClientIps, err := j.getInboundClientIps(email) clientIpsRecord, err := j.getInboundClientIps(email)
if err != nil { if err != nil {
j.addInboundClientIps(email, ips) j.addInboundClientIps(email, ips)
continue continue
} }
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog
} }
return shouldCleanLog return shouldCleanLog
@@ -309,12 +309,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) { func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
db := database.GetDB() db := database.GetDB()
var inbounds *model.Inbound inbound := &model.Inbound{}
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error err := db.Model(&model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").First(inbound).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
return inbounds, nil return inbound, nil
} }

View File

@@ -23,7 +23,7 @@ func (j *CheckCpuJob) Run() {
threshold, _ := j.settingService.GetTgCpu() threshold, _ := j.settingService.GetTgCpu()
// get latest status of server // get latest status of server
percent, err := cpu.Percent(1*time.Second, false) percent, err := cpu.Percent(1*time.Minute, false)
if err == nil && percent[0] > float64(threshold) { if err == nil && percent[0] > float64(threshold) {
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold", msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64), "Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),

View File

@@ -1,11 +1,16 @@
package job package job
import ( import (
"encoding/json"
"x-ui/logger" "x-ui/logger"
"x-ui/web/service" "x-ui/web/service"
"x-ui/xray"
"github.com/valyala/fasthttp"
) )
type XrayTrafficJob struct { type XrayTrafficJob struct {
settingService service.SettingService
xrayService service.XrayService xrayService service.XrayService
inboundService service.InboundService inboundService service.InboundService
outboundService service.OutboundService outboundService service.OutboundService
@@ -31,7 +36,36 @@ func (j *XrayTrafficJob) Run() {
if err != nil { if err != nil {
logger.Warning("add outbound traffic failed:", err) logger.Warning("add outbound traffic failed:", err)
} }
if ExternalTrafficInformEnable, err := j.settingService.GetExternalTrafficInformEnable(); ExternalTrafficInformEnable {
j.informTrafficToExternalAPI(traffics, clientTraffics)
} else if err != nil {
logger.Warning("get ExternalTrafficInformEnable failed:", err)
}
if needRestart0 || needRestart1 { if needRestart0 || needRestart1 {
j.xrayService.SetToNeedRestart() j.xrayService.SetToNeedRestart()
} }
} }
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
informURL, err := j.settingService.GetExternalTrafficInformURI()
if err != nil {
logger.Warning("get ExternalTrafficInformURI failed:", err)
return
}
requestBody, err := json.Marshal(map[string]interface{}{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
if err != nil {
logger.Warning("parse client/inbound traffic failed:", err)
return
}
request := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(request)
request.Header.SetMethod("POST")
request.Header.SetContentType("application/json; charset=UTF-8")
request.SetBody([]byte(requestBody))
request.SetRequestURI(informURL)
response := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(response)
if err := fasthttp.Do(request, response); err != nil {
logger.Warning("POST ExternalTrafficInformURI failed:", err)
}
}

View File

@@ -51,8 +51,8 @@
"system": { "system": {
"statsInboundDownlink": true, "statsInboundDownlink": true,
"statsInboundUplink": true, "statsInboundUplink": true,
"statsOutboundDownlink": true, "statsOutboundDownlink": false,
"statsOutboundUplink": true "statsOutboundUplink": false
} }
}, },
"routing": { "routing": {

View File

@@ -285,9 +285,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
continue continue
} }
if (major == 1 && minor == 8 && patch == 24) || if major > 25 || (major == 25 && minor > 3) || (major == 25 && minor == 3 && patch >= 3) {
(major == 24 && ((minor > 11) || (minor == 11 && patch >= 30))) ||
(major > 24) {
versions = append(versions, release.TagName) versions = append(versions, release.TagName)
} }
} }

View File

@@ -24,50 +24,52 @@ import (
var xrayTemplateConfig string var xrayTemplateConfig string
var defaultValueMap = map[string]string{ var defaultValueMap = map[string]string{
"xrayTemplateConfig": xrayTemplateConfig, "xrayTemplateConfig": xrayTemplateConfig,
"webListen": "", "webListen": "",
"webDomain": "", "webDomain": "",
"webPort": "2053", "webPort": "2053",
"webCertFile": "", "webCertFile": "",
"webKeyFile": "", "webKeyFile": "",
"secret": random.Seq(32), "secret": random.Seq(32),
"webBasePath": "/", "webBasePath": "/",
"sessionMaxAge": "60", "sessionMaxAge": "60",
"pageSize": "50", "pageSize": "50",
"expireDiff": "0", "expireDiff": "0",
"trafficDiff": "0", "trafficDiff": "0",
"remarkModel": "-ieo", "remarkModel": "-ieo",
"timeLocation": "Asia/Tehran", "timeLocation": "Local",
"tgBotEnable": "false", "tgBotEnable": "false",
"tgBotToken": "", "tgBotToken": "",
"tgBotProxy": "", "tgBotProxy": "",
"tgBotAPIServer": "", "tgBotAPIServer": "",
"tgBotChatId": "", "tgBotChatId": "",
"tgRunTime": "@daily", "tgRunTime": "@daily",
"tgBotBackup": "false", "tgBotBackup": "false",
"tgBotLoginNotify": "true", "tgBotLoginNotify": "true",
"tgCpu": "80", "tgCpu": "80",
"tgLang": "en-US", "tgLang": "en-US",
"secretEnable": "false", "secretEnable": "false",
"subEnable": "false", "subEnable": "false",
"subListen": "", "subListen": "",
"subPort": "2096", "subPort": "2096",
"subPath": "/sub/", "subPath": "/sub/",
"subDomain": "", "subDomain": "",
"subCertFile": "", "subCertFile": "",
"subKeyFile": "", "subKeyFile": "",
"subUpdates": "12", "subUpdates": "12",
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "true", "subShowInfo": "true",
"subURI": "", "subURI": "",
"subJsonPath": "/json/", "subJsonPath": "/json/",
"subJsonURI": "", "subJsonURI": "",
"subJsonFragment": "", "subJsonFragment": "",
"subJsonNoises": "", "subJsonNoises": "",
"subJsonMux": "", "subJsonMux": "",
"subJsonRules": "", "subJsonRules": "",
"datepicker": "gregorian", "datepicker": "gregorian",
"warp": "", "warp": "",
"externalTrafficInformEnable": "false",
"externalTrafficInformURI": "",
} }
type SettingService struct{} type SettingService struct{}
@@ -496,6 +498,22 @@ func (s *SettingService) SetWarp(data string) error {
return s.setString("warp", data) return s.setString("warp", data)
} }
func (s *SettingService) GetExternalTrafficInformEnable() (bool, error) {
return s.getBool("externalTrafficInformEnable")
}
func (s *SettingService) SetExternalTrafficInformEnable(value bool) error {
return s.setBool("externalTrafficInformEnable", value)
}
func (s *SettingService) GetExternalTrafficInformURI() (string, error) {
return s.getString("externalTrafficInformURI")
}
func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
return s.setString("externalTrafficInformURI", InformURI)
}
func (s *SettingService) GetIpLimitEnable() (bool, error) { func (s *SettingService) GetIpLimitEnable() (bool, error) {
accessLogPath, err := xray.GetAccessLogPath() accessLogPath, err := xray.GetAccessLogPath()
if err != nil { if err != nil {

View File

@@ -307,8 +307,6 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
onlyMessage = true onlyMessage = true
if isAdmin { if isAdmin {
if len(commandArgs) == 0 { if len(commandArgs) == 0 {
msg += t.I18nBot("tgbot.commands.restartUsage")
} else if strings.ToLower(commandArgs[0]) == "force" {
if t.xrayService.IsXrayRunning() { if t.xrayService.IsXrayRunning() {
err := t.xrayService.RestartXray(true) err := t.xrayService.RestartXray(true)
if err != nil { if err != nil {

View File

@@ -10,38 +10,41 @@ import (
) )
const ( const (
loginUser = "LOGIN_USER" loginUserKey = "LOGIN_USER"
defaultPath = "/" defaultPath = "/"
) )
func init() { func init() {
gob.Register(model.User{}) gob.Register(model.User{})
} }
func SetLoginUser(c *gin.Context, user *model.User) error { func SetLoginUser(c *gin.Context, user *model.User) {
if user == nil {
return
}
s := sessions.Default(c) s := sessions.Default(c)
s.Set(loginUser, user) s.Set(loginUserKey, *user)
return s.Save()
} }
func SetMaxAge(c *gin.Context, maxAge int) error { func SetMaxAge(c *gin.Context, maxAge int) {
s := sessions.Default(c) s := sessions.Default(c)
s.Options(sessions.Options{ s.Options(sessions.Options{
Path: defaultPath, Path: defaultPath,
MaxAge: maxAge, MaxAge: maxAge,
HttpOnly: true, HttpOnly: true,
}) })
return s.Save()
} }
func GetLoginUser(c *gin.Context) *model.User { func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c) s := sessions.Default(c)
obj := s.Get(loginUser) obj := s.Get(loginUserKey)
if obj == nil { if obj == nil {
return nil return nil
} }
user, ok := obj.(model.User) user, ok := obj.(model.User)
if !ok { if !ok {
s.Delete(loginUserKey)
return nil return nil
} }
return &user return &user
@@ -51,7 +54,7 @@ func IsLogin(c *gin.Context) bool {
return GetLoginUser(c) != nil return GetLoginUser(c) != nil
} }
func ClearSession(c *gin.Context) error { func ClearSession(c *gin.Context) {
s := sessions.Default(c) s := sessions.Default(c)
s.Clear() s.Clear()
s.Options(sessions.Options{ s.Options(sessions.Options{
@@ -59,5 +62,4 @@ func ClearSession(c *gin.Context) error {
MaxAge: -1, MaxAge: -1,
HttpOnly: true, HttpOnly: true,
}) })
return s.Save()
} }

View File

@@ -44,6 +44,7 @@
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Digital Certificate" "certificate" = "Digital Certificate"
"fail" = "Failed" "fail" = "Failed"
"comment" = "Comment"
"success" = "Successfully" "success" = "Successfully"
"getVersion" = "Get Version" "getVersion" = "Get Version"
"install" = "Install" "install" = "Install"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path." "secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path."
"secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path." "secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path."
"secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path." "secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path."
"emptyDnsDesc" = "No added DNS servers."
"emptyFakeDnsDesc" = "No added Fake DNS servers."
"emptyBalancersDesc" = "No added balancers."
"emptyReverseDesc" = "No added reverse proxies."
[menu] [menu]
"dashboard" = "Overview" "dashboard" = "Overview"
@@ -108,9 +113,10 @@
"config" = "Config" "config" = "Config"
"backup" = "Backup & Restore" "backup" = "Backup & Restore"
"backupTitle" = "Database Backup & Restore" "backupTitle" = "Database Backup & Restore"
"backupDescription" = "It is recommended to make a backup before restoring a database."
"exportDatabase" = "Back Up" "exportDatabase" = "Back Up"
"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device."
"importDatabase" = "Restore" "importDatabase" = "Restore"
"importDatabaseDesc" = "Click to select and upload a .db file from your device to restore your database from a backup."
[pages.inbounds] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." "subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI" "subURI" = "Reverse Proxy URI"
"subURIDesc" = "The URI path of the subscription URL for use behind proxies." "subURIDesc" = "The URI path of the subscription URL for use behind proxies."
"externalTrafficInformEnable" = "External Traffic Inform"
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
"externalTrafficInformURI" = "External Traffic Inform URI"
"externalTrafficInformURIDesc" = "Traffic updates are sent to this URI."
"fragment" = "Fragmentation" "fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet." "fragmentDesc" = "Enable fragmentation for TLS hello packet."
"fragmentSett" = "Fragmentation Settings" "fragmentSett" = "Fragmentation Settings"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Settings" "muxSett" = "Mux Settings"
"direct" = "Direct Connection" "direct" = "Direct Connection"
"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country." "directDesc" = "Directly establishes connections with domains or IP ranges of a specific country."
"notifications" = "Notifications"
"certs" = "Certificaties"
"externalTraffic" = "External Traffic"
"dateAndTime" = "Date and Time"
"proxyAndServer" = "Proxy and Server"
"intervals" = "Intervals"
"information" = "Information"
"language" = "Language"
"telegramBotLanguage" = "Telegram Bot Language"
[pages.xray] [pages.xray]
"title" = "Xray Configs" "title" = "Xray Configs"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Whether to enable DNS query logs" "dnsLogDesc" = "Whether to enable DNS query logs"
"maskAddress" = "Mask Address" "maskAddress" = "Mask Address"
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log." "maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
"statistics" = "Statistics"
"statsInboundUplink" = "Inbound Upload Statistics"
"statsInboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all inbound proxies."
"statsInboundDownlink" = "Inbound Download Statistics"
"statsInboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all inbound proxies."
"statsOutboundUplink" = "Outbound Upload Statistics"
"statsOutboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all outbound proxies."
"statsOutboundDownlink" = "Outbound Download Statistics"
"statsOutboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all outbound proxies."
[pages.xray.rules] [pages.xray.rules]
"first" = "First" "first" = "First"
@@ -426,6 +453,14 @@
"enableDesc" = "Enable built-in DNS server" "enableDesc" = "Enable built-in DNS server"
"tag" = "DNS Inbound Tag" "tag" = "DNS Inbound Tag"
"tagDesc" = "This tag will be available as an Inbound tag in routing rules." "tagDesc" = "This tag will be available as an Inbound tag in routing rules."
"clientIp" = "Client IP"
"clientIpDesc" = "Used to notify the server of the specified IP location during DNS queries"
"disableCache" = "Disable cache"
"disableCacheDesc" = "Disables DNS caching"
"disableFallback" = "Disable Fallback"
"disableFallbackDesc" = "Disables fallback DNS queries"
"disableFallbackIfMatch" = "Disable Fallback If Match"
"disableFallbackIfMatchDesc" = "Disables fallback DNS queries when the matching domain list of the DNS server is hit"
"strategy" = "Query Strategy" "strategy" = "Query Strategy"
"strategyDesc" = "Overall strategy to resolve domain names" "strategyDesc" = "Overall strategy to resolve domain names"
"add" = "Add Server" "add" = "Add Server"
@@ -440,7 +475,7 @@
"poolSize" = "Pool Size" "poolSize" = "Pool Size"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Admin credentials"
"secret" = "Secret Token" "secret" = "Secret Token"
"loginSecurity" = "Secure Login" "loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security." "loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
@@ -483,9 +518,9 @@
"status" = "✅ Bot is OK!" "status" = "✅ Bot is OK!"
"usage" = "❗ Please provide a text to search!" "usage" = "❗ Please provide a text to search!"
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>" "getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "To restart Xray Core:\r\n<code>/restart force</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>" "helpAdminCommands" = "To restart Xray Core:\r\n<code>/restart</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>" "helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operation successful!" "restartSuccess" = "✅ Operation successful!"
"restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core is not running." "xrayNotRunning" = "❗ Xray Core is not running."

View File

@@ -44,6 +44,7 @@
"monitor" = "Listening IP" "monitor" = "Listening IP"
"certificate" = "Certificado Digital" "certificate" = "Certificado Digital"
"fail" = "Falló" "fail" = "Falló"
"comment" = "Comentario"
"success" = "Éxito" "success" = "Éxito"
"getVersion" = "Obtener versión" "getVersion" = "Obtener versión"
"install" = "Instalar" "install" = "Instalar"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja." "secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja."
"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." "secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
"secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." "secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
"emptyDnsDesc" = "No hay servidores DNS añadidos."
"emptyFakeDnsDesc" = "No hay servidores Fake DNS añadidos."
"emptyBalancersDesc" = "No hay balanceadores añadidos."
"emptyReverseDesc" = "No hay proxies inversos añadidos."
[menu] [menu]
"dashboard" = "Estado del Sistema" "dashboard" = "Estado del Sistema"
@@ -108,9 +113,10 @@
"config" = "Configuración" "config" = "Configuración"
"backup" = "Copia de Seguridad y Restauración" "backup" = "Copia de Seguridad y Restauración"
"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos" "backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos"
"backupDescription" = "Recuerda hacer una copia de seguridad antes de importar una nueva base de datos." "exportDatabase" = "Copia de seguridad"
"exportDatabase" = "Descargar Base de Datos" "exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo."
"importDatabase" = "Cargar Base de Datos" "importDatabase" = "Restaurar"
"importDatabaseDesc" = "Haz clic para seleccionar y cargar un archivo .db desde tu dispositivo para restaurar tu base de datos desde una copia de seguridad."
[pages.inbounds] [pages.inbounds]
"title" = "Entradas" "title" = "Entradas"
@@ -305,6 +311,10 @@
"subShowInfo" = "Mostrar información de uso" "subShowInfo" = "Mostrar información de uso"
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." "subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
"subURI" = "URI de proxy inverso" "subURI" = "URI de proxy inverso"
"externalTrafficInformEnable" = "Informe de tráfico externo"
"externalTrafficInformEnableDesc" = "Informar a la API externa sobre cada actualización de tráfico."
"externalTrafficInformURI" = "URI de información de tráfico externo"
"externalTrafficInformURIDesc" = "Las actualizaciones de tráfico se envían a este URI."
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy" "subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
"fragment" = "Fragmentación" "fragment" = "Fragmentación"
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS" "fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"
@@ -316,7 +326,15 @@
"muxSett" = "Configuración Mux" "muxSett" = "Configuración Mux"
"direct" = "Conexión Directa" "direct" = "Conexión Directa"
"directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico." "directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico."
"notifications" = "Notificaciones"
"certs" = "Certificados"
"externalTraffic" = "Tráfico Externo"
"dateAndTime" = "Fecha y Hora"
"proxyAndServer" = "Proxy y Servidor"
"intervals" = "Intervalos"
"information" = "Información"
"language" = "Idioma"
"telegramBotLanguage" = "Idioma del Bot de Telegram"
[pages.xray] [pages.xray]
"title" = "Xray Configuración" "title" = "Xray Configuración"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Si habilitar los registros de consulta DNS" "dnsLogDesc" = "Si habilitar los registros de consulta DNS"
"maskAddress" = "Enmascarar Dirección" "maskAddress" = "Enmascarar Dirección"
"maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro." "maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro."
"statistics" = "Estadísticas"
"statsInboundUplink" = "Estadísticas de Subida de Entrada"
"statsInboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de entrada."
"statsInboundDownlink" = "Estadísticas de Bajada de Entrada"
"statsInboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de entrada."
"statsOutboundUplink" = "Estadísticas de Subida de Salida"
"statsOutboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de salida."
"statsOutboundDownlink" = "Estadísticas de Bajada de Salida"
"statsOutboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de salida."
[pages.xray.rules] [pages.xray.rules]
"first" = "Primero" "first" = "Primero"
@@ -426,6 +453,14 @@
"enableDesc" = "Habilitar servidor DNS incorporado" "enableDesc" = "Habilitar servidor DNS incorporado"
"tag" = "Etiqueta de Entrada DNS" "tag" = "Etiqueta de Entrada DNS"
"tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento." "tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento."
"clientIp" = "IP del cliente"
"clientIpDesc" = "Se utiliza para notificar al servidor la ubicación IP especificada durante las consultas DNS"
"disableCache" = "Desactivar caché"
"disableCacheDesc" = "Desactiva el almacenamiento en caché de DNS"
"disableFallback" = "Desactivar respaldo"
"disableFallbackDesc" = "Desactiva las consultas DNS de respaldo"
"disableFallbackIfMatch" = "Desactivar respaldo si coincide"
"disableFallbackIfMatchDesc" = "Desactiva las consultas DNS de respaldo cuando se acierta en la lista de dominios coincidentes del servidor DNS"
"strategy" = "Estrategia de Consulta" "strategy" = "Estrategia de Consulta"
"strategyDesc" = "Estrategia general para resolver nombres de dominio" "strategyDesc" = "Estrategia general para resolver nombres de dominio"
"add" = "Agregar Servidor" "add" = "Agregar Servidor"
@@ -440,7 +475,7 @@
"poolSize" = "Tamaño del grupo" "poolSize" = "Tamaño del grupo"
[pages.settings.security] [pages.settings.security]
"admin" = "Administrador" "admin" = "Credenciales de administrador"
"secret" = "Token Secreto" "secret" = "Token Secreto"
"loginSecurity" = "Seguridad de Inicio de Sesión" "loginSecurity" = "Seguridad de Inicio de Sesión"
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios." "loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
@@ -483,9 +518,9 @@
"status" = "✅ ¡El bot está bien!" "status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!" "usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"getID" = "🆔 Tu ID: <code>{{ .ID }}</code>" "getID" = "🆔 Tu ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Para reiniciar Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>" "helpAdminCommands" = "Para reiniciar Xray Core:\r\n<code>/restart</code>\r\n\r\nPara buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>" "helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ ¡Operación exitosa!" "restartSuccess" = "✅ ¡Operación exitosa!"
"restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core no está en ejecución." "xrayNotRunning" = "❗ Xray Core no está en ejecución."

View File

@@ -44,6 +44,7 @@
"monitor" = "آی‌پی اتصال" "monitor" = "آی‌پی اتصال"
"certificate" = "گواهی دیجیتال" "certificate" = "گواهی دیجیتال"
"fail" = "ناموفق" "fail" = "ناموفق"
"comment" = "توضیحات"
"success" = "موفق" "success" = "موفق"
"getVersion" = "دریافت نسخه" "getVersion" = "دریافت نسخه"
"install" = "نصب" "install" = "نصب"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"emptyDnsDesc" = "هیچ سرور DNS اضافه نشده است."
"emptyFakeDnsDesc" = "هیچ سرور Fake DNS اضافه نشده است."
"emptyBalancersDesc" = "هیچ بالانسر اضافه نشده است."
"emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است."
[menu] [menu]
"dashboard" = "نمای کلی" "dashboard" = "نمای کلی"
@@ -108,9 +113,10 @@
"config" = "پیکربندی" "config" = "پیکربندی"
"backup" = "پشتیبان‌گیری" "backup" = "پشتیبان‌گیری"
"backupTitle" = "پشتیبان‌گیری دیتابیس" "backupTitle" = "پشتیبان‌گیری دیتابیس"
"backupDescription" = "توصیه‌می‌شود قبل‌از واردکردن یک دیتابیس جدید، نسخه پشتیبان تهیه ‌کنید"
"exportDatabase" = "پشتیبان‌گیری" "exportDatabase" = "پشتیبان‌گیری"
"importDatabase" = ازگرداندن" "exportDatabaseDesc" = رای دانلود یک فایل .db حاوی پشتیبان از پایگاه داده فعلی خود به دستگاهتان کلیک کنید."
"importDatabase" = "بازیابی"
"importDatabaseDesc" = "برای انتخاب و آپلود یک فایل .db از دستگاهتان و بازیابی پایگاه داده از یک پشتیبان کلیک کنید."
[pages.inbounds] [pages.inbounds]
"title" = "کاربران" "title" = "کاربران"
@@ -300,6 +306,10 @@
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌" "subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌"
"subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن" "subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن"
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت" "subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت"
"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک"
"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
"subEncrypt" = "کدگذاری" "subEncrypt" = "کدگذاری"
"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه" "subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
"subShowInfo" = "نمایش اطلاعات مصرف" "subShowInfo" = "نمایش اطلاعات مصرف"
@@ -316,7 +326,15 @@
"muxSett" = "تنظیمات ماکس" "muxSett" = "تنظیمات ماکس"
"direct" = "اتصال مستقیم" "direct" = "اتصال مستقیم"
"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند" "directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند"
"notifications" = "اعلان‌ها"
"certs" = "گواهی‌ها"
"externalTraffic" = "ترافیک خارجی"
"dateAndTime" = "تاریخ و زمان"
"proxyAndServer" = "پراکسی و سرور"
"intervals" = "فواصل"
"information" = "اطلاعات"
"language" = "زبان"
"telegramBotLanguage" = "زبان ربات تلگرام"
[pages.xray] [pages.xray]
"title" = "پیکربندی ایکس‌ری" "title" = "پیکربندی ایکس‌ری"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید" "dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید"
"maskAddress" = "پنهان کردن آدرس" "maskAddress" = "پنهان کردن آدرس"
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند." "maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند."
"statistics" = "آمار"
"statsInboundUplink" = "آمار آپلود ورودی"
"statsInboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های ورودی را فعال می‌کند."
"statsInboundDownlink" = "آمار دانلود ورودی"
"statsInboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های ورودی را فعال می‌کند."
"statsOutboundUplink" = "آمار آپلود خروجی"
"statsOutboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های خروجی را فعال می‌کند."
"statsOutboundDownlink" = "آمار دانلود خروجی"
"statsOutboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های خروجی را فعال می‌کند."
[pages.xray.rules] [pages.xray.rules]
"first" = "اولین" "first" = "اولین"
@@ -426,6 +453,14 @@
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید" "enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
"tag" = "برچسب" "tag" = "برچسب"
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود" "tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
"clientIp" = "آی‌پی کلاینت"
"clientIpDesc" = "برای اطلاع‌رسانی به سرور درباره مکان IP مشخص‌شده در طول درخواست‌های DNS استفاده می‌شود"
"disableCache" = "غیرفعال‌سازی کش"
"disableCacheDesc" = "کش DNS را غیرفعال می‌کند"
"disableFallback" = "غیرفعال‌سازی Fallback"
"disableFallbackDesc" = "درخواست‌های DNS Fallback را غیرفعال می‌کند"
"disableFallbackIfMatch" = "غیرفعال‌سازی Fallback در صورت تطابق"
"disableFallbackIfMatchDesc" = "درخواست‌های DNS Fallback را زمانی که لیست دامنه‌های مطابقت‌یافته سرور DNS فعال است، غیرفعال می‌کند"
"strategy" = "استراتژی پرس‌وجو" "strategy" = "استراتژی پرس‌وجو"
"strategyDesc" = "استراتژی کلی برای حل نام دامنه" "strategyDesc" = "استراتژی کلی برای حل نام دامنه"
"add" = "افزودن سرور" "add" = "افزودن سرور"
@@ -440,7 +475,7 @@
"poolSize" = "اندازه استخر" "poolSize" = "اندازه استخر"
[pages.settings.security] [pages.settings.security]
"admin" = "مدیر" "admin" = "اعتبارنامه‌های ادمین"
"secret" = "توکن مخفی" "secret" = "توکن مخفی"
"loginSecurity" = "ورود ایمن" "loginSecurity" = "ورود ایمن"
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند" "loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
@@ -483,9 +518,9 @@
"status" = "✅ ربات در حالت عادی است!" "status" = "✅ ربات در حالت عادی است!"
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>" "getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
"helpAdminCommands" = "برای راه‌اندازی مجدد Xray Core:\r\n<code>/restart force</code>\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>" "helpAdminCommands" = "برای راه‌اندازی مجدد Xray Core:\r\n<code>/restart</code>\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
"helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n<code>/usage [ایمیل]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>" "helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n<code>/usage [ایمیل]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ عملیات با موفقیت انجام شد!" "restartSuccess" = "✅ عملیات با موفقیت انجام شد!"
"restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>." "restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست." "xrayNotRunning" = "❗ Xray Core در حال اجرا نیست."

View File

@@ -44,6 +44,7 @@
"monitor" = "IP Pemantauan" "monitor" = "IP Pemantauan"
"certificate" = "Sertifikat Digital" "certificate" = "Sertifikat Digital"
"fail" = "Gagal" "fail" = "Gagal"
"comment" = "Komentar"
"success" = "Berhasil" "success" = "Berhasil"
"getVersion" = "Dapatkan Versi" "getVersion" = "Dapatkan Versi"
"install" = "Instal" "install" = "Instal"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks." "secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks."
"secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks." "secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks."
"secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks." "secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks."
"emptyDnsDesc" = "Tidak ada server DNS yang ditambahkan."
"emptyFakeDnsDesc" = "Tidak ada server Fake DNS yang ditambahkan."
"emptyBalancersDesc" = "Tidak ada penyeimbang yang ditambahkan."
"emptyReverseDesc" = "Tidak ada proxy terbalik yang ditambahkan."
[menu] [menu]
"dashboard" = "Ikhtisar" "dashboard" = "Ikhtisar"
@@ -108,9 +113,10 @@
"config" = "Konfigurasi" "config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan" "backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database" "backupTitle" = "Cadangan & Pulihkan Database"
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
"exportDatabase" = "Cadangkan" "exportDatabase" = "Cadangkan"
"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda."
"importDatabase" = "Pulihkan" "importDatabase" = "Pulihkan"
"importDatabaseDesc" = "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan."
[pages.inbounds] [pages.inbounds]
"title" = "Masuk" "title" = "Masuk"
@@ -305,7 +311,10 @@
"subShowInfo" = "Tampilkan Info Penggunaan" "subShowInfo" = "Tampilkan Info Penggunaan"
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." "subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
"subURI" = "URI Proxy Terbalik" "subURI" = "URI Proxy Terbalik"
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy." "externalTrafficInformEnable" = "Informasikan API eksternal pada setiap pembaruan lalu lintas."
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
"externalTrafficInformURI" = "Lalu Lintas Eksternal Menginformasikan URI"
"externalTrafficInformURIDesc" = "Pembaruan lalu lintas dikirim ke URI ini."
"fragment" = "Fragmentasi" "fragment" = "Fragmentasi"
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS" "fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
"fragmentSett" = "Pengaturan Fragmentasi" "fragmentSett" = "Pengaturan Fragmentasi"
@@ -316,7 +325,15 @@
"muxSett" = "Pengaturan Mux" "muxSett" = "Pengaturan Mux"
"direct" = "Koneksi langsung" "direct" = "Koneksi langsung"
"directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu." "directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu."
"notifications" = "Notifikasi"
"certs" = "Sertifikat"
"externalTraffic" = "Lalu Lintas Eksternal"
"dateAndTime" = "Tanggal dan Waktu"
"proxyAndServer" = "Proxy dan Server"
"intervals" = "Interval"
"information" = "Informasi"
"language" = "Bahasa"
"telegramBotLanguage" = "Bahasa Bot Telegram"
[pages.xray] [pages.xray]
"title" = "Konfigurasi Xray" "title" = "Konfigurasi Xray"
@@ -369,6 +386,15 @@
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS" "dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
"maskAddress" = "Alamat Masker" "maskAddress" = "Alamat Masker"
"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log." "maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log."
"statistics" = "Statistik"
"statsInboundUplink" = "Statistik Unggah Masuk"
"statsInboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy masuk."
"statsInboundDownlink" = "Statistik Unduh Masuk"
"statsInboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy masuk."
"statsOutboundUplink" = "Statistik Unggah Keluar"
"statsOutboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy keluar."
"statsOutboundDownlink" = "Statistik Unduh Keluar"
"statsOutboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy keluar."
[pages.xray.rules] [pages.xray.rules]
"first" = "Pertama" "first" = "Pertama"
@@ -426,6 +452,14 @@
"enableDesc" = "Aktifkan server DNS bawaan" "enableDesc" = "Aktifkan server DNS bawaan"
"tag" = "Tanda DNS Masuk" "tag" = "Tanda DNS Masuk"
"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan." "tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan."
"clientIp" = "IP Klien"
"clientIpDesc" = "Digunakan untuk memberi tahu server tentang lokasi IP yang ditentukan selama kueri DNS"
"disableCache" = "Nonaktifkan cache"
"disableCacheDesc" = "Menonaktifkan caching DNS"
"disableFallback" = "Nonaktifkan Fallback"
"disableFallbackDesc" = "Menonaktifkan kueri DNS fallback"
"disableFallbackIfMatch" = "Nonaktifkan Fallback Jika Cocok"
"disableFallbackIfMatchDesc" = "Menonaktifkan kueri DNS fallback ketika daftar domain yang cocok dari server DNS terpenuhi"
"strategy" = "Strategi Kueri" "strategy" = "Strategi Kueri"
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain" "strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
"add" = "Tambahkan Server" "add" = "Tambahkan Server"
@@ -440,7 +474,7 @@
"poolSize" = "Ukuran Kolam" "poolSize" = "Ukuran Kolam"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Kredensial admin"
"secret" = "Token Rahasia" "secret" = "Token Rahasia"
"loginSecurity" = "Login Aman" "loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih." "loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
@@ -483,9 +517,9 @@
"status" = "✅ Bot dalam keadaan baik!" "status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!" "usage" = "❗ Harap berikan teks untuk mencari!"
"getID" = "🆔 ID Anda: <code>{{ .ID }}</code>" "getID" = "🆔 ID Anda: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n<code>/restart force</code>\r\n\r\nUntuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>" "helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n<code>/restart</code>\r\n\r\nUntuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n<code>/usage [Email]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>" "helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n<code>/usage [Email]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operasi berhasil!" "restartSuccess" = "✅ Operasi berhasil!"
"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core tidak berjalan." "xrayNotRunning" = "❗ Xray Core tidak berjalan."

View File

@@ -44,6 +44,7 @@
"monitor" = "監視" "monitor" = "監視"
"certificate" = "証明書" "certificate" = "証明書"
"fail" = "失敗" "fail" = "失敗"
"comment" = "コメント"
"success" = "成功" "success" = "成功"
"getVersion" = "バージョン取得" "getVersion" = "バージョン取得"
"install" = "インストール" "install" = "インストール"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。"
"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
"emptyDnsDesc" = "追加されたDNSサーバーはありません。"
"emptyFakeDnsDesc" = "追加されたFake DNSサーバーはありません。"
"emptyBalancersDesc" = "追加されたバランサーはありません。"
"emptyReverseDesc" = "追加されたリバースプロキシはありません。"
[menu] [menu]
"dashboard" = "ダッシュボード" "dashboard" = "ダッシュボード"
@@ -108,9 +113,10 @@
"config" = "設定" "config" = "設定"
"backup" = "バックアップと復元" "backup" = "バックアップと復元"
"backupTitle" = "データベースのバックアップと復元" "backupTitle" = "データベースのバックアップと復元"
"backupDescription" = "データベースを復元する前にバックアップすることをお勧めします"
"exportDatabase" = "バックアップ" "exportDatabase" = "バックアップ"
"exportDatabaseDesc" = "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。"
"importDatabase" = "復元" "importDatabase" = "復元"
"importDatabaseDesc" = "クリックして、デバイスから .db ファイルを選択し、アップロードしてバックアップからデータベースを復元します。"
[pages.inbounds] [pages.inbounds]
"title" = "インバウンド一覧" "title" = "インバウンド一覧"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する" "subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する"
"subURI" = "リバースプロキシURI" "subURI" = "リバースプロキシURI"
"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する" "subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する"
"externalTrafficInformEnable" = "外部トラフィック情報"
"externalTrafficInformEnableDesc" = "トラフィックの更新ごとに外部 API に通知します。"
"externalTrafficInformURI" = "外部トラフィック通知 URI"
"externalTrafficInformURIDesc" = "トラフィックの更新ごとに外部 API に通知します。"
"fragment" = "フラグメント" "fragment" = "フラグメント"
"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする" "fragmentDesc" = "TLS helloパケットのフラグメントを有効にする"
"fragmentSett" = "設定" "fragmentSett" = "設定"
@@ -316,7 +326,15 @@
"muxSett" = "マルチプレクサ設定" "muxSett" = "マルチプレクサ設定"
"direct" = "直接接続" "direct" = "直接接続"
"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する" "directDesc" = "特定の国のドメインまたはIP範囲に直接接続する"
"notifications" = "通知"
"certs" = "証明書"
"externalTraffic" = "外部トラフィック"
"dateAndTime" = "日付と時刻"
"proxyAndServer" = "プロキシとサーバー"
"intervals" = "間隔"
"information" = "情報"
"language" = "言語"
"telegramBotLanguage" = "Telegram Botの言語"
[pages.xray] [pages.xray]
"title" = "Xray 設定" "title" = "Xray 設定"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "DNSクエリのログを有効にするかどうか" "dnsLogDesc" = "DNSクエリのログを有効にするかどうか"
"maskAddress" = "アドレスをマスク" "maskAddress" = "アドレスをマスク"
"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます" "maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます"
"statistics" = "統計"
"statsInboundUplink" = "インバウンドアップロード統計"
"statsInboundUplinkDesc" = "すべてのインバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
"statsInboundDownlink" = "インバウンドダウンロード統計"
"statsInboundDownlinkDesc" = "すべてのインバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
"statsOutboundUplink" = "アウトバウンドアップロード統計"
"statsOutboundUplinkDesc" = "すべてのアウトバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
"statsOutboundDownlink" = "アウトバウンドダウンロード統計"
"statsOutboundDownlinkDesc" = "すべてのアウトバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
[pages.xray.rules] [pages.xray.rules]
"first" = "最初" "first" = "最初"
@@ -426,6 +453,14 @@
"enableDesc" = "組み込みDNSサーバーを有効にする" "enableDesc" = "組み込みDNSサーバーを有効にする"
"tag" = "DNSインバウンドタグ" "tag" = "DNSインバウンドタグ"
"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます" "tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます"
"clientIp" = "クライアントIP"
"clientIpDesc" = "DNSクエリ中に指定されたIPの位置をサーバーに通知するために使用されます"
"disableCache" = "キャッシュを無効にする"
"disableCacheDesc" = "DNSキャッシュを無効にします"
"disableFallback" = "フォールバックを無効にする"
"disableFallbackDesc" = "フォールバックDNSクエリを無効にします"
"disableFallbackIfMatch" = "一致した場合にフォールバックを無効にする"
"disableFallbackIfMatchDesc" = "DNSサーバーの一致するドメインリストにヒットした場合、フォールバックDNSクエリを無効にします"
"strategy" = "クエリ戦略" "strategy" = "クエリ戦略"
"strategyDesc" = "ドメイン名解決の全体的な戦略" "strategyDesc" = "ドメイン名解決の全体的な戦略"
"add" = "サーバー追加" "add" = "サーバー追加"
@@ -440,7 +475,7 @@
"poolSize" = "プールサイズ" "poolSize" = "プールサイズ"
[pages.settings.security] [pages.settings.security]
"admin" = "管理者" "admin" = "管理者の資格情報"
"secret" = "セキュリティトークン" "secret" = "セキュリティトークン"
"loginSecurity" = "ログインセキュリティ" "loginSecurity" = "ログインセキュリティ"
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる" "loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
@@ -483,9 +518,9 @@
"status" = "✅ ボットは正常に動作しています!" "status" = "✅ ボットは正常に動作しています!"
"usage" = "❗ 検索するテキストを入力してください!" "usage" = "❗ 検索するテキストを入力してください!"
"getID" = "🆔 あなたのIDは<code>{{ .ID }}</code>" "getID" = "🆔 あなたのIDは<code>{{ .ID }}</code>"
"helpAdminCommands" = "Xray Coreを再起動するには\r\n<code>/restart force</code>\r\n\r\nクライアントの電子メールを検索するには\r\n<code>/usage [電子メール]</code>\r\n\r\nインバウンドクライアントの統計情報を含むを検索するには\r\n<code>/inbound [備考]</code>\r\n\r\nTelegramチャットID\r\n<code>/id</code>" "helpAdminCommands" = "Xray Coreを再起動するには\r\n<code>/restart</code>\r\n\r\nクライアントの電子メールを検索するには\r\n<code>/usage [電子メール]</code>\r\n\r\nインバウンドクライアントの統計情報を含むを検索するには\r\n<code>/inbound [備考]</code>\r\n\r\nTelegramチャットID\r\n<code>/id</code>"
"helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n<code>/usage [電子メール]</code>\r\n\r\nTelegramチャットID\r\n<code>/id</code>" "helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n<code>/usage [電子メール]</code>\r\n\r\nTelegramチャットID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>" "restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>"
"xrayNotRunning" = "❗ Xray Core は動作していません。" "xrayNotRunning" = "❗ Xray Core は動作していません。"

View File

@@ -44,6 +44,7 @@
"monitor" = "IP de Escuta" "monitor" = "IP de Escuta"
"certificate" = "Certificado Digital" "certificate" = "Certificado Digital"
"fail" = "Falhou" "fail" = "Falhou"
"comment" = "Comentário"
"success" = "Com Sucesso" "success" = "Com Sucesso"
"getVersion" = "Obter Versão" "getVersion" = "Obter Versão"
"install" = "Instalar" "install" = "Instalar"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo." "secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo."
"secAlertSubURI" = "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo." "secAlertSubURI" = "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo."
"secAlertSubJsonURI" = "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo." "secAlertSubJsonURI" = "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo."
"emptyDnsDesc" = "Nenhum servidor DNS adicionado."
"emptyFakeDnsDesc" = "Nenhum servidor Fake DNS adicionado."
"emptyBalancersDesc" = "Nenhum balanceador adicionado."
"emptyReverseDesc" = "Nenhum proxy reverso adicionado."
[menu] [menu]
"dashboard" = "Visão Geral" "dashboard" = "Visão Geral"
@@ -108,9 +113,10 @@
"config" = "Configuração" "config" = "Configuração"
"backup" = "Backup e Restauração" "backup" = "Backup e Restauração"
"backupTitle" = "Backup e Restauração do Banco de Dados" "backupTitle" = "Backup e Restauração do Banco de Dados"
"backupDescription" = "É recomendado fazer um backup antes de restaurar o banco de dados." "exportDatabase" = "Backup"
"exportDatabase" = "Fazer Backup" "exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo."
"importDatabase" = "Restaurar" "importDatabase" = "Restaurar"
"importDatabaseDesc" = "Clique para selecionar e enviar um arquivo .db do seu dispositivo para restaurar seu banco de dados a partir de um backup."
[pages.inbounds] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente." "subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente."
"subURI" = "URI de Proxy Reverso" "subURI" = "URI de Proxy Reverso"
"subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies." "subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies."
"externalTrafficInformEnable" = "Informações de tráfego externo"
"externalTrafficInformEnableDesc" = "Informar a API externa sobre cada atualização de tráfego."
"externalTrafficInformURI" = "URI de informação de tráfego externo"
"externalTrafficInformURIDesc" = "As atualizações de tráfego são enviadas para este URI."
"fragment" = "Fragmentação" "fragment" = "Fragmentação"
"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello." "fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello."
"fragmentSett" = "Configurações de Fragmentação" "fragmentSett" = "Configurações de Fragmentação"
@@ -316,7 +326,15 @@
"muxSett" = "Configurações de Mux" "muxSett" = "Configurações de Mux"
"direct" = "Conexão Direta" "direct" = "Conexão Direta"
"directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico." "directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico."
"notifications" = "Notificações"
"certs" = "Certificados"
"externalTraffic" = "Tráfego Externo"
"dateAndTime" = "Data e Hora"
"proxyAndServer" = "Proxy e Servidor"
"intervals" = "Intervalos"
"information" = "Informação"
"language" = "Idioma"
"telegramBotLanguage" = "Idioma do Bot do Telegram"
[pages.xray] [pages.xray]
"title" = "Configurações Xray" "title" = "Configurações Xray"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Se ativar logs de consulta DNS" "dnsLogDesc" = "Se ativar logs de consulta DNS"
"maskAddress" = "Mascarar Endereço" "maskAddress" = "Mascarar Endereço"
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log." "maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log."
"statistics" = "Estatísticas"
"statsInboundUplink" = "Estatísticas de Upload de Entrada"
"statsInboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de entrada."
"statsInboundDownlink" = "Estatísticas de Download de Entrada"
"statsInboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de entrada."
"statsOutboundUplink" = "Estatísticas de Upload de Saída"
"statsOutboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de saída."
"statsOutboundDownlink" = "Estatísticas de Download de Saída"
"statsOutboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de saída."
[pages.xray.rules] [pages.xray.rules]
"first" = "Primeiro" "first" = "Primeiro"
@@ -426,6 +453,14 @@
"enableDesc" = "Ativar o servidor DNS integrado" "enableDesc" = "Ativar o servidor DNS integrado"
"tag" = "Tag de Entrada DNS" "tag" = "Tag de Entrada DNS"
"tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento." "tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento."
"clientIp" = "IP do Cliente"
"clientIpDesc" = "Usado para notificar o servidor sobre a localização IP especificada durante consultas DNS"
"disableCache" = "Desativar cache"
"disableCacheDesc" = "Desativa o cache de DNS"
"disableFallback" = "Desativar Fallback"
"disableFallbackDesc" = "Desativa consultas DNS de fallback"
"disableFallbackIfMatch" = "Desativar Fallback Se Corresponder"
"disableFallbackIfMatchDesc" = "Desativa consultas DNS de fallback quando a lista de domínios correspondentes do servidor DNS é atingida"
"strategy" = "Estratégia de Consulta" "strategy" = "Estratégia de Consulta"
"strategyDesc" = "Estratégia geral para resolver nomes de domínio" "strategyDesc" = "Estratégia geral para resolver nomes de domínio"
"add" = "Adicionar Servidor" "add" = "Adicionar Servidor"
@@ -440,7 +475,7 @@
"poolSize" = "Tamanho do Pool" "poolSize" = "Tamanho do Pool"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Credenciais de administrador"
"secret" = "Token Secreto" "secret" = "Token Secreto"
"loginSecurity" = "Login Seguro" "loginSecurity" = "Login Seguro"
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança." "loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
@@ -483,9 +518,9 @@
"status" = "✅ Bot está OK!" "status" = "✅ Bot está OK!"
"usage" = "❗ Por favor, forneça um texto para pesquisar!" "usage" = "❗ Por favor, forneça um texto para pesquisar!"
"getID" = "🆔 Seu ID: <code>{{ .ID }}</code>" "getID" = "🆔 Seu ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>" "helpAdminCommands" = "Para reiniciar o Xray Core:\r\n<code>/restart</code>\r\n\r\nPara pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>" "helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operação bem-sucedida!" "restartSuccess" = "✅ Operação bem-sucedida!"
"restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>." "restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core não está em execução." "xrayNotRunning" = "❗ Xray Core não está em execução."

View File

@@ -41,16 +41,17 @@
"offline" = "Офлайн" "offline" = "Офлайн"
"online" = "Онлайн" "online" = "Онлайн"
"domainName" = "Домен" "domainName" = "Домен"
"monitor" = "Порт IP" "monitor" = "Слушать IP"
"certificate" = "Цифровой сертификат" "certificate" = "Цифровой сертификат"
"fail" = "Неудачно" "fail" = "Ошибка"
"comment" = "Комментарий"
"success" = "Успешно" "success" = "Успешно"
"getVersion" = "Узнать версию" "getVersion" = "Узнать версию"
"install" = "Установка" "install" = "Установка"
"clients" = "Клиенты" "clients" = "Клиенты"
"usage" = "Использование" "usage" = "Использование"
"secretToken" = "Секретный токен" "secretToken" = "Секретный токен"
"remained" = "остались" "remained" = "Остаток"
"security" = "Безопасность" "security" = "Безопасность"
"secAlertTitle" = "Предупреждение системы безопасности" "secAlertTitle" = "Предупреждение системы безопасности"
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных" "secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь."
"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
"emptyDnsDesc" = "Нет добавленных DNS-серверов."
"emptyFakeDnsDesc" = "Нет добавленных Fake DNS-серверов."
"emptyBalancersDesc" = "Нет добавленных балансировщиков."
"emptyReverseDesc" = "Нет добавленных обратных прокси."
[menu] [menu]
"dashboard" = "Статус системы" "dashboard" = "Статус системы"
@@ -78,7 +83,7 @@
"invalidFormData" = "Недопустимый формат данных" "invalidFormData" = "Недопустимый формат данных"
"emptyUsername" = "Введите имя пользователя" "emptyUsername" = "Введите имя пользователя"
"emptyPassword" = "Введите пароль" "emptyPassword" = "Введите пароль"
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль" "wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен."
"successLogin" = "Успешный вход" "successLogin" = "Успешный вход"
[pages.index] [pages.index]
@@ -88,33 +93,34 @@
"xrayStatus" = "Xray" "xrayStatus" = "Xray"
"stopXray" = "Остановить" "stopXray" = "Остановить"
"restartXray" = "Перезапустить" "restartXray" = "Перезапустить"
"xraySwitch" = "Версия" "xraySwitch" = "Выбор версии"
"xraySwitchClick" = "Выберите желаемую версию" "xraySwitchClick" = "Выберите желаемую версию"
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями" "xraySwitchClickDesk" = "Обратите внимание: старые версии могут не поддерживать текущие настройки"
"operationHours" = "Время работы системы" "operationHours" = "Время работы системы"
"systemLoad" = "Системная нагрузка" "systemLoad" = "Нагрузка на систему"
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут" "systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам." "connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам." "connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
"connectionCount" = "Количество соединений" "connectionCount" = "Количество соединений"
"upSpeed" = "Общая скорость upload для всех сетей" "upSpeed" = "Общая скорость отправки для всех сетей"
"downSpeed" = "Общая скорость download для всех сетей" "downSpeed" = "Общая скорость загрузки для всех сетей"
"totalSent" = "Общий объем загруженных данных для всех сетей с момента запуска системы" "totalSent" = "Общий объем отправляемых данных с момента запуска системы"
"totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы." "totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы."
"xraySwitchVersionDialog" = "Переключить версию Xray" "xraySwitchVersionDialog" = "Переключить версию Xray"
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" "xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
"dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу" "dontRefresh" = "Установка в процессе. Не обновляйте страницу"
"logs" = "Логи" "logs" = "Логи"
"config" = "Конфигурация" "config" = "Конфигурация"
"backup" = "Бэкап и восстановление" "backup" = "Резервное копирование и восстановление"
"backupTitle" = "База данных бэкапа и восстановления" "backupTitle" = "База данных резервных копий"
"backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных"
"exportDatabase" = "Экспорт базы данных" "exportDatabase" = "Экспорт базы данных"
"exportDatabaseDesc" = "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство."
"importDatabase" = "Импорт базы данных" "importDatabase" = "Импорт базы данных"
"importDatabaseDesc" = "Нажмите, чтобы выбрать и загрузить файл .db с вашего устройства для восстановления базы данных из резервной копии."
[pages.inbounds] [pages.inbounds]
"title" = "Подключения" "title" = "Подключения"
"totalDownUp" = "Всего uploads/downloads" "totalDownUp" = "Общий объем отправленного/полученного трафика"
"totalUsage" = "Всего использовано" "totalUsage" = "Всего использовано"
"inboundCount" = "Количество подключений" "inboundCount" = "Количество подключений"
"operate" = "Меню" "operate" = "Меню"
@@ -146,17 +152,17 @@
"meansNoLimit" = "= Без ограничений (значение: ГБ)" "meansNoLimit" = "= Без ограничений (значение: ГБ)"
"totalFlow" = "Общий расход" "totalFlow" = "Общий расход"
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало" "leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
"noRecommendKeepDefault" = "Не рекомендуется оставлять настройки по умолчанию" "noRecommendKeepDefault" = "Рекомендуется оставить настройки по умолчанию"
"certificatePath" = "Путь к файлу" "certificatePath" = "Путь к файлу"
"certificateContent" = "Содержимое файла" "certificateContent" = "Содержимое файла"
"publicKey" = "Публичный ключ" "publicKey" = "Публичный ключ"
"privatekey" = "Приватный ключ" "privatekey" = "Закрытый ключ"
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать" "clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
"client" = "Клиент" "client" = "Клиент"
"export" = "Экспорт ключей" "export" = "Экспорт ключей"
"clone" = "Клонировать" "clone" = "Клонировать"
"cloneInbound" = "Клонировать" "cloneInbound" = "Клонировать"
"cloneInboundContent" = "Все настройки этого подключения, кроме порта, IP-адреса прослушки и клиентов, будут клонированы" "cloneInboundContent" = "Будут клонированы все настройки подключений, за исключением списка клиентов, порта и IP-адреса прослушивания"
"cloneInboundOk" = "Клонировано" "cloneInboundOk" = "Клонировано"
"resetAllTraffic" = "Сбросить трафик всех подключений" "resetAllTraffic" = "Сбросить трафик всех подключений"
"resetAllTrafficTitle" = "Сброс трафика всех подключений" "resetAllTrafficTitle" = "Сброс трафика всех подключений"
@@ -172,8 +178,8 @@
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?" "delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Пожалуйста, укажите уникальный Email" "emailDesc" = "Пожалуйста, укажите уникальный Email"
"IPLimit" = "Ограничение по IP" "IPLimit" = "Лимит по IP"
"IPLimitDesc" = "Сбросить подключение, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)" "IPLimitDesc" = "Ограничение количества подключений с одного IP (0 отключить)"
"IPLimitlog" = "IP лог" "IPLimitlog" = "IP лог"
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)" "IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
"IPLimitlogclear" = "Очистить лог" "IPLimitlogclear" = "Очистить лог"
@@ -228,18 +234,18 @@
"save" = "Сохранить" "save" = "Сохранить"
"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу" "infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
"restartPanel" = "Перезапуск панели" "restartPanel" = "Перезапуск панели"
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере" "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель станет недоступной, проверьте лог сервера"
"actions" = "Действия" "actions" = "Действия"
"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию" "resetDefaultConfig" = "Восстановить настройки по умолчанию"
"panelSettings" = "Настройки панели" "panelSettings" = "Настройки панели"
"securitySettings" = "Настройки безопасности" "securitySettings" = "Настройки безопасности"
"TGBotSettings" = "Настройки Telegram бота" "TGBotSettings" = "Настройки Telegram бота"
"panelListeningIP" = "IP-адрес панели" "panelListeningIP" = "IP-адрес панели"
"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP"
"panelListeningDomain" = "Домен прослушивания панели" "panelListeningDomain" = "Домен панели"
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса" "panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
"panelPort" = "Порт панели" "panelPort" = "Порт панели"
"panelPortDesc" = "Порт, используемый для отображения этой панели" "panelPortDesc" = "Порт, на котором работает панель"
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" "publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'" "publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели" "privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
@@ -251,14 +257,14 @@
"remarkModel" = "Модель примечания и символ разделения" "remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "Выбор даты" "datepicker" = "Выбор даты"
"datepickerPlaceholder" = "Выберите дату" "datepickerPlaceholder" = "Выберите дату"
"datepickerDescription" = "Запланированные задачи выполняются в соответствии с данным календарём" "datepickerDescription" = "Запланированные задачи будут выполняться в выбранное время"
"sampleRemark" = "Пример замечания" "sampleRemark" = "Пример замечания"
"oldUsername" = "Текущий логин" "oldUsername" = "Текущий логин"
"currentPassword" = "Текущий пароль" "currentPassword" = "Текущий пароль"
"newUsername" = "Новый логин" "newUsername" = "Новый логин"
"newPassword" = "Новый пароль" "newPassword" = "Новый пароль"
"telegramBotEnable" = "Включить Telegram бота" "telegramBotEnable" = "Включить Telegram бота"
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота" "telegramBotEnableDesc" = "Получайте доступ к функциям панели через Telegram-бота"
"telegramToken" = "Токен Telegram бота" "telegramToken" = "Токен Telegram бота"
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" "telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
"telegramProxy" = "Прокси Socks5" "telegramProxy" = "Прокси Socks5"
@@ -268,14 +274,14 @@
"telegramChatId" = "Идентификатор Telegram администратора бота" "telegramChatId" = "Идентификатор Telegram администратора бота"
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте." "telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте."
"telegramNotifyTime" = "Частота уведомлений бота Telegram" "telegramNotifyTime" = "Частота уведомлений бота Telegram"
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab" "telegramNotifyTimeDesc" = "Укажите интервал уведомлений в формате Crontab"
"tgNotifyBackup" = "Резервное копирование базы данных" "tgNotifyBackup" = "Резервное копирование базы данных"
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете" "tgNotifyBackupDesc" = "Отправлять уведомление с файлом резервной копии базы данных"
"tgNotifyLogin" = "Уведомление о входе" "tgNotifyLogin" = "Уведомление о входе"
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель." "tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
"sessionMaxAge" = "Продолжительность сессии" "sessionMaxAge" = "Продолжительность сессии"
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)" "sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
"expireTimeDiff" = "Порог истечения срока сессии для уведомления" "expireTimeDiff" = "Задержка уведомления об истечении сессии"
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)" "expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
"trafficDiff" = "Порог трафика для уведомления" "trafficDiff" = "Порог трафика для уведомления"
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)" "trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
@@ -303,20 +309,32 @@
"subEncrypt" = "Шифровать конфиги" "subEncrypt" = "Шифровать конфиги"
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке" "subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
"subShowInfo" = "Показать информацию об использовании" "subShowInfo" = "Показать информацию об использовании"
"subShowInfoDesc" = "Показывать оставшиеся трафик и дату после имени конфигурации" "subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации"
"subURI" = "URI обратного прокси" "subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" "subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"externalTrafficInformEnable" = "Информация о внешнем трафике"
"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
"externalTrafficInformURI" = "URI информации о внешнем трафике"
"externalTrafficInformURIDesc" = "Обновления трафика отправляются на этот URI"
"fragment" = "Фрагментация" "fragment" = "Фрагментация"
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS" "fragmentDesc" = "Включить фрагментацию TLS-приветствия"
"fragmentSett" = "Настройки фрагментации" "fragmentSett" = "Настройки фрагментации"
"noisesDesc" = "Включить Noises." "noisesDesc" = "Включить Noises."
"noisesSett" = "Настройки Noises" "noisesSett" = "Настройки Noises"
"mux" = "Mux" "mux" = "Mux"
"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных." "muxDesc" = "Передача нескольких независимых потоков данных в одном соединении."
"muxSett" = "Mux Настройки" "muxSett" = "Mux Настройки"
"direct" = "Прямая связь" "direct" = "Прямая связь"
"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны." "directDesc" = "Устанавливает прямые соединения с доменами или IP-адресами определённой страны."
"notifications" = "Уведомления"
"certs" = "Сертификаты"
"externalTraffic" = "Внешний трафик"
"dateAndTime" = "Дата и время"
"proxyAndServer" = "Прокси и сервер"
"intervals" = "Интервалы"
"information" = "Информация"
"language" = "Язык"
"telegramBotLanguage" = "Язык Telegram-бота"
[pages.xray] [pages.xray]
"title" = "Настройки Xray" "title" = "Настройки Xray"
@@ -327,7 +345,7 @@
"generalConfigs" = "Основные настройки" "generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки" "generalConfigsDesc" = "Эти параметры описывают общие настройки"
"logConfigs" = "Журнал" "logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их только в случае необходимости!" "logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!"
"blockConfigs" = "Блокировка конфигураций" "blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам" "blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"basicRouting" = "Базовые соединения" "basicRouting" = "Базовые соединения"
@@ -368,7 +386,16 @@
"dnsLog" = "DNS Журнал" "dnsLog" = "DNS Журнал"
"dnsLogDesc" = "Включить логи запросов DNS" "dnsLogDesc" = "Включить логи запросов DNS"
"maskAddress" = "Маскировать Адрес" "maskAddress" = "Маскировать Адрес"
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе." "maskAddressDesc" = "При активации реальный IP-адрес заменяется на маскировочный в логах."
"statistics" = "Статистика"
"statsInboundUplink" = "Статистика входящего аплинка"
"statsInboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех входящих прокси."
"statsInboundDownlink" = "Статистика входящего даунлинка"
"statsInboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех входящих прокси."
"statsOutboundUplink" = "Статистика исходящего аплинка"
"statsOutboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех исходящих прокси."
"statsOutboundDownlink" = "Статистика исходящего даунлинка"
"statsOutboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех исходящих прокси."
[pages.xray.rules] [pages.xray.rules]
"first" = "Первый" "first" = "Первый"
@@ -414,7 +441,7 @@
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." "balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Приватный ключ" "secretKey" = "Закрытый ключ"
"publicKey" = "Публичный ключ" "publicKey" = "Публичный ключ"
"allowedIPs" = "Разрешенные IP-адреса" "allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка" "endpoint" = "Конечная точка"
@@ -426,6 +453,14 @@
"enableDesc" = "Включить встроенный DNS-сервер" "enableDesc" = "Включить встроенный DNS-сервер"
"tag" = "Входящий тег DNS" "tag" = "Входящий тег DNS"
"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации." "tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации."
"clientIp" = "IP клиента"
"clientIpDesc" = "Используется для уведомления сервера о указанном местоположении IP во время DNS-запросов"
"disableCache" = "Отключить кэш"
"disableCacheDesc" = "Отключает кэширование DNS"
"disableFallback" = "Отключить резервный DNS"
"disableFallbackDesc" = "Отключает резервные DNS-запросы"
"disableFallbackIfMatch" = "Отключить резервный DNS при совпадении"
"disableFallbackIfMatchDesc" = "Отключает резервные DNS-запросы при совпадении списка доменов DNS-сервера"
"strategy" = "Стратегия запроса" "strategy" = "Стратегия запроса"
"strategyDesc" = "Общая стратегия разрешения доменных имен" "strategyDesc" = "Общая стратегия разрешения доменных имен"
"add" = "Добавить сервер" "add" = "Добавить сервер"
@@ -440,7 +475,7 @@
"poolSize" = "Размер пула" "poolSize" = "Размер пула"
[pages.settings.security] [pages.settings.security]
"admin" = "Админ" "admin" = "Учетные данные администратора"
"secret" = "Секретный токен" "secret" = "Секретный токен"
"loginSecurity" = "Безопасность входа" "loginSecurity" = "Безопасность входа"
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя" "loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
@@ -448,7 +483,7 @@
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui" "secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Изменение настроек" "modifySettings" = "Настройки изменены"
"getSettings" = "Просмотр настроек" "getSettings" = "Просмотр настроек"
"modifyUser" = "Изменение пользователя" "modifyUser" = "Изменение пользователя"
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль" "originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
@@ -483,9 +518,9 @@
"status" = "✅ Бот работает нормально!" "status" = "✅ Бот работает нормально!"
"usage" = "❗ Пожалуйста, укажите текст для поиска!" "usage" = "❗ Пожалуйста, укажите текст для поиска!"
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>" "getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Для перезапуска Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>" "helpAdminCommands" = "Для перезапуска Xray Core:\r\n<code>/restart</code>\r\n\r\nДля поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Для поиска статистики используйте следующую команду:\r\n<code>/usage [Email]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>" "helpClientCommands" = "Для поиска статистики используйте следующую команду:\r\n<code>/usage [Email]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Операция успешно завершена!" "restartSuccess" = "✅ Операция успешно завершена!"
"restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>." "restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущен." "xrayNotRunning" = "❗ Xray Core не запущен."
@@ -572,7 +607,7 @@
"allClients" = "Все клиенты" "allClients" = "Все клиенты"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Успешный!" "successfulOperation" = "✅ Успешно!"
"errorOperation" = "❗ Ошибка в операции." "errorOperation" = "❗ Ошибка в операции."
"getInboundsFailed" = "❌ Не удалось получить входящие потоки." "getInboundsFailed" = "❌ Не удалось получить входящие потоки."
"getClientsFailed" = "❌ Не удалось получить клиентов." "getClientsFailed" = "❌ Не удалось получить клиентов."
@@ -581,8 +616,8 @@
"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены." "IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены."
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен." "TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен."
"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен." "resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен."
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит Трафик успешно сохранен." "setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит трафика успешно установлен."
"expireResetSuccess" = "✅ {{ .Email }}: Дни истечения успешно сброшены." "expireResetSuccess" = "✅ {{ .Email }}: Срок действия успешно сброшен."
"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен." "resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен."
"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены." "clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены."
"getIpLog" = "✅ {{ .Email }}: Получен лог IP." "getIpLog" = "✅ {{ .Email }}: Получен лог IP."

View File

@@ -44,6 +44,7 @@
"monitor" = "Dinleme IP" "monitor" = "Dinleme IP"
"certificate" = "Dijital Sertifika" "certificate" = "Dijital Sertifika"
"fail" = "Başarısız" "fail" = "Başarısız"
"comment" = "Yorum"
"success" = "Başarılı" "success" = "Başarılı"
"getVersion" = "Sürümü Al" "getVersion" = "Sürümü Al"
"install" = "Yükle" "install" = "Yükle"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." "secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
"secAlertSubURI" = "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." "secAlertSubURI" = "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
"secAlertSubJsonURI" = "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." "secAlertSubJsonURI" = "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
"emptyDnsDesc" = "Eklenmiş DNS sunucusu yok."
"emptyFakeDnsDesc" = "Eklenmiş Fake DNS sunucusu yok."
"emptyBalancersDesc" = "Eklenmiş dengeleyici yok."
"emptyReverseDesc" = "Eklenmiş ters proxy yok."
[menu] [menu]
"dashboard" = "Genel Bakış" "dashboard" = "Genel Bakış"
@@ -108,9 +113,10 @@
"config" = "Yapılandırma" "config" = "Yapılandırma"
"backup" = "Yedekle & Geri Yükle" "backup" = "Yedekle & Geri Yükle"
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme" "backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"
"backupDescription" = "Veritabanını geri yüklemeden önce yedek almanız önerilir."
"exportDatabase" = "Yedekle" "exportDatabase" = "Yedekle"
"exportDatabaseDesc" = "Mevcut veritabanınızın yedeğini içeren bir .db dosyasını cihazınıza indirmek için tıklayın."
"importDatabase" = "Geri Yükle" "importDatabase" = "Geri Yükle"
"importDatabaseDesc" = "Cihazınızdan bir .db dosyası seçip yükleyerek veritabanınızı yedekten geri yüklemek için tıklayın."
[pages.inbounds] [pages.inbounds]
"title" = "Gelenler" "title" = "Gelenler"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir." "subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir."
"subURI" = "Ters Proxy URI" "subURI" = "Ters Proxy URI"
"subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu." "subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu."
"externalTrafficInformEnable" = "Harici Trafik Bilgisi"
"externalTrafficInformEnableDesc" = "Her trafik güncellemesinde harici API'yi bilgilendirin."
"externalTrafficInformURI" = "Harici Trafik Bilgisi URI'si"
"externalTrafficInformURIDesc" = "Trafik güncellemeleri bu URI'ye gönderildi."
"fragment" = "Parçalama" "fragment" = "Parçalama"
"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir." "fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir."
"fragmentSett" = "Parçalama Ayarları" "fragmentSett" = "Parçalama Ayarları"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Ayarları" "muxSett" = "Mux Ayarları"
"direct" = "Doğrudan Bağlantı" "direct" = "Doğrudan Bağlantı"
"directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar." "directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar."
"notifications" = "Bildirimler"
"certs" = "Sertifikalar"
"externalTraffic" = "Harici Trafik"
"dateAndTime" = "Tarih ve Saat"
"proxyAndServer" = "Proxy ve Sunucu"
"intervals" = "Aralıklar"
"information" = "Bilgi"
"language" = "Dil"
"telegramBotLanguage" = "Telegram Bot Dili"
[pages.xray] [pages.xray]
"title" = "Xray Yapılandırmaları" "title" = "Xray Yapılandırmaları"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin" "dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
"maskAddress" = "Adres Maskesi" "maskAddress" = "Adres Maskesi"
"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir." "maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir."
"statistics" = "İstatistikler"
"statsInboundUplink" = "Gelen Yükleme İstatistikleri"
"statsInboundUplinkDesc" = "Tüm gelen proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir."
"statsInboundDownlink" = "Gelen İndirme İstatistikleri"
"statsInboundDownlinkDesc" = "Tüm gelen proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir."
"statsOutboundUplink" = "Giden Yükleme İstatistikleri"
"statsOutboundUplinkDesc" = "Tüm giden proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir."
"statsOutboundDownlink" = "Giden İndirme İstatistikleri"
"statsOutboundDownlinkDesc" = "Tüm giden proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir."
[pages.xray.rules] [pages.xray.rules]
"first" = "İlk" "first" = "İlk"
@@ -426,6 +453,14 @@
"enableDesc" = "Dahili DNS sunucusunu etkinleştir" "enableDesc" = "Dahili DNS sunucusunu etkinleştir"
"tag" = "DNS Gelen Etiketi" "tag" = "DNS Gelen Etiketi"
"tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir." "tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir."
"clientIp" = "İstemci IP"
"clientIpDesc" = "DNS sorguları sırasında belirtilen IP konumunu sunucuya bildirmek için kullanılır"
"disableCache" = "Önbelleği devre dışı bırak"
"disableCacheDesc" = "DNS önbelleğini devre dışı bırakır"
"disableFallback" = "Yedeklemeyi devre dışı bırak"
"disableFallbackDesc" = "Yedek DNS sorgularını devre dışı bırakır"
"disableFallbackIfMatch" = "Eşleşirse Yedeklemeyi Devre Dışı Bırak"
"disableFallbackIfMatchDesc" = "DNS sunucusunun eşleşen alan adı listesi vurulduğunda yedek DNS sorgularını devre dışı bırakır"
"strategy" = "Sorgu Stratejisi" "strategy" = "Sorgu Stratejisi"
"strategyDesc" = "Alan adlarını çözmek için genel strateji" "strategyDesc" = "Alan adlarını çözmek için genel strateji"
"add" = "Sunucu Ekle" "add" = "Sunucu Ekle"
@@ -440,7 +475,7 @@
"poolSize" = "Havuz Boyutu" "poolSize" = "Havuz Boyutu"
[pages.settings.security] [pages.settings.security]
"admin" = "Yönetici" "admin" = "Yönetici kimlik bilgileri"
"secret" = "Gizli Anahtar" "secret" = "Gizli Anahtar"
"loginSecurity" = "Güvenli Giriş" "loginSecurity" = "Güvenli Giriş"
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler." "loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
@@ -483,9 +518,9 @@
"status" = "✅ Bot çalışıyor!" "status" = "✅ Bot çalışıyor!"
"usage" = "❗ Lütfen aramak için bir metin sağlayın!" "usage" = "❗ Lütfen aramak için bir metin sağlayın!"
"getID" = "🆔 Kimliğiniz: <code>{{ .ID }}</code>" "getID" = "🆔 Kimliğiniz: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n<code>/restart force</code>\r\n\r\nBir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>" "helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n<code>/restart</code>\r\n\r\nBir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
"helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n<code>/usage [E-posta]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>" "helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n<code>/usage [E-posta]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ İşlem başarılı!" "restartSuccess" = "✅ İşlem başarılı!"
"restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>." "restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core çalışmıyor." "xrayNotRunning" = "❗ Xray Core çalışmıyor."

View File

@@ -44,6 +44,7 @@
"monitor" = "Слухати IP" "monitor" = "Слухати IP"
"certificate" = "Цифровий сертифікат" "certificate" = "Цифровий сертифікат"
"fail" = "Помилка" "fail" = "Помилка"
"comment" = "Коментар"
"success" = "Успішно" "success" = "Успішно"
"getVersion" = "Отримати версію" "getVersion" = "Отримати версію"
"install" = "Встановити" "install" = "Встановити"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"emptyDnsDesc" = "Немає доданих DNS-серверів."
"emptyFakeDnsDesc" = "Немає доданих Fake DNS-серверів."
"emptyBalancersDesc" = "Немає доданих балансувальників."
"emptyReverseDesc" = "Немає доданих зворотних проксі."
[menu] [menu]
"dashboard" = "Огляд" "dashboard" = "Огляд"
@@ -108,9 +113,10 @@
"config" = "Конфігурація" "config" = "Конфігурація"
"backup" = "Резервне копіювання та відновлення" "backup" = "Резервне копіювання та відновлення"
"backupTitle" = "Резервне копіювання та відновлення бази даних" "backupTitle" = "Резервне копіювання та відновлення бази даних"
"backupDescription" = "Рекомендується зробити резервну копію перед відновленням бази даних." "exportDatabase" = "Резервна копія"
"exportDatabase" = "Резервне копіювання" "exportDatabaseDesc" = "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій."
"importDatabase" = "Відновити" "importDatabase" = "Відновити"
"importDatabaseDesc" = "Натисніть, щоб вибрати та завантажити файл .db з вашого пристрою для відновлення бази даних з резервної копії."
[pages.inbounds] [pages.inbounds]
"title" = "Вхідні" "title" = "Вхідні"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах." "subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах."
"subURI" = "URI зворотного проксі" "subURI" = "URI зворотного проксі"
"subURIDesc" = "URI до URL-адреси підписки для використання за проксі." "subURIDesc" = "URI до URL-адреси підписки для використання за проксі."
"externalTrafficInformEnable" = "Інформація про зовнішній трафік"
"externalTrafficInformEnableDesc" = "Інформувати зовнішній API про кожне оновлення трафіку."
"externalTrafficInformURI" = "Інформаційний URI зовнішнього трафіку"
"externalTrafficInformURIDesc" = "Оновлення трафіку надсилаються на цей URI."
"fragment" = "Фрагментація" "fragment" = "Фрагментація"
"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS" "fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS"
"fragmentSett" = "Параметри фрагментації" "fragmentSett" = "Параметри фрагментації"
@@ -316,7 +326,15 @@
"muxSett" = "Налаштування Mux" "muxSett" = "Налаштування Mux"
"direct" = "Пряме підключення" "direct" = "Пряме підключення"
"directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни." "directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни."
"notifications" = "Сповіщення"
"certs" = "Сертифікати"
"externalTraffic" = "Зовнішній трафік"
"dateAndTime" = "Дата та час"
"proxyAndServer" = "Проксі та сервер"
"intervals" = "Інтервали"
"information" = "Інформація"
"language" = "Мова"
"telegramBotLanguage" = "Мова Telegram-бота"
[pages.xray] [pages.xray]
"title" = "Xray конфігурації" "title" = "Xray конфігурації"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Чи включити журнали запитів DNS" "dnsLogDesc" = "Чи включити журнали запитів DNS"
"maskAddress" = "Маскувати Адресу" "maskAddress" = "Маскувати Адресу"
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі." "maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
"statistics" = "Статистика"
"statsInboundUplink" = "Статистика вхідного аплінку"
"statsInboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вхідних проксі."
"statsInboundDownlink" = "Статистика вхідного даунлінку"
"statsInboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вхідних проксі."
"statsOutboundUplink" = "Статистика вихідного аплінку"
"statsOutboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вихідних проксі."
"statsOutboundDownlink" = "Статистика вихідного даунлінку"
"statsOutboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вихідних проксі."
[pages.xray.rules] [pages.xray.rules]
"first" = "Перший" "first" = "Перший"
@@ -426,6 +453,14 @@
"enableDesc" = "Увімкнути вбудований DNS-сервер" "enableDesc" = "Увімкнути вбудований DNS-сервер"
"tag" = "Мітка вхідного DNS" "tag" = "Мітка вхідного DNS"
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації." "tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
"clientIp" = "IP клієнта"
"clientIpDesc" = "Використовується для повідомлення серверу про вказане місцезнаходження IP під час DNS-запитів"
"disableCache" = "Вимкнути кеш"
"disableCacheDesc" = "Вимкнути кешування DNS"
"disableFallback" = "Вимкнути резервний DNS"
"disableFallbackDesc" = "Вимкнути резервні DNS-запити"
"disableFallbackIfMatch" = "Вимкнути резервний DNS при збігу"
"disableFallbackIfMatchDesc" = "Вимкнути резервні DNS-запити при збігу списку доменів DNS-сервера"
"strategy" = "Стратегія запиту" "strategy" = "Стратегія запиту"
"strategyDesc" = "Загальна стратегія вирішення доменних імен" "strategyDesc" = "Загальна стратегія вирішення доменних імен"
"add" = "Додати сервер" "add" = "Додати сервер"
@@ -440,7 +475,7 @@
"poolSize" = "Розмір пулу" "poolSize" = "Розмір пулу"
[pages.settings.security] [pages.settings.security]
"admin" = "Адміністратор" "admin" = "Облікові дані адміністратора"
"secret" = "Секретний маркер" "secret" = "Секретний маркер"
"loginSecurity" = "Безпечний вхід" "loginSecurity" = "Безпечний вхід"
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки." "loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
@@ -483,9 +518,9 @@
"status" = "✅ Бот в порядку!" "status" = "✅ Бот в порядку!"
"usage" = "❗ Введіть текст для пошуку!" "usage" = "❗ Введіть текст для пошуку!"
"getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>" "getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Для перезапуску Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>" "helpAdminCommands" = "Для перезапуску Xray Core:\r\n<code>/restart</code>\r\n\r\nДля пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>" "helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Операція успішна!" "restartSuccess" = "✅ Операція успішна!"
"restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>." "restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущений." "xrayNotRunning" = "❗ Xray Core не запущений."

View File

@@ -44,6 +44,7 @@
"monitor" = "Listening IP" "monitor" = "Listening IP"
"certificate" = "Chứng chỉ số" "certificate" = "Chứng chỉ số"
"fail" = "Thất bại" "fail" = "Thất bại"
"comment" = "Bình luận"
"success" = "Thành công" "success" = "Thành công"
"getVersion" = "Lấy phiên bản" "getVersion" = "Lấy phiên bản"
"install" = "Cài đặt" "install" = "Cài đặt"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." "secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
"secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." "secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
"secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." "secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
"emptyDnsDesc" = "Không có máy chủ DNS nào được thêm."
"emptyFakeDnsDesc" = "Không có máy chủ Fake DNS nào được thêm."
"emptyBalancersDesc" = "Không có bộ cân bằng tải nào được thêm."
"emptyReverseDesc" = "Không có proxy ngược nào được thêm."
[menu] [menu]
"dashboard" = "Trạng thái hệ thống" "dashboard" = "Trạng thái hệ thống"
@@ -108,9 +113,10 @@
"config" = "Cấu hình" "config" = "Cấu hình"
"backup" = "Sao lưu & Khôi phục" "backup" = "Sao lưu & Khôi phục"
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu" "backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu"
"backupDescription" = "Hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới." "exportDatabase" = "Sao lưu"
"exportDatabase" = "Tải về Cơ sở dữ liệu" "exportDatabaseDesc" = "Nhấp để tải xuống tệp .db chứa bản sao lưu cơ sở dữ liệu hiện tại của bạn vào thiết bị."
"importDatabase" = "Tải lên Cơ sở dữ liệu" "importDatabase" = "Khôi phục"
"importDatabaseDesc" = "Nhấp để chọn và tải lên tệp .db từ thiết bị của bạn để khôi phục cơ sở dữ liệu từ bản sao lưu."
[pages.inbounds] [pages.inbounds]
"title" = "Điểm vào (Inbounds)" "title" = "Điểm vào (Inbounds)"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" "subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
"subURI" = "URI proxy trung gian" "subURI" = "URI proxy trung gian"
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian" "subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
"externalTrafficInformEnable" = "Thông báo giao thông bên ngoài"
"externalTrafficInformEnableDesc" = "Thông báo cho API bên ngoài về mọi cập nhật lưu lượng truy cập."
"externalTrafficInformURI" = "URI thông báo lưu lượng truy cập bên ngoài"
"externalTrafficInformURIDesc" = "Cập nhật lưu lượng truy cập được gửi tới URI này."
"fragment" = "Sự phân mảnh" "fragment" = "Sự phân mảnh"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello" "fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
"fragmentSett" = "Cài đặt phân mảnh" "fragmentSett" = "Cài đặt phân mảnh"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Cài đặt" "muxSett" = "Mux Cài đặt"
"direct" = "Kết nối trực tiếp" "direct" = "Kết nối trực tiếp"
"directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể." "directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể."
"notifications" = "Thông báo"
"certs" = "Chứng chỉ"
"externalTraffic" = "Lưu lượng bên ngoài"
"dateAndTime" = "Ngày và giờ"
"proxyAndServer" = "Proxy và máy chủ"
"intervals" = "Khoảng thời gian"
"information" = "Thông tin"
"language" = "Ngôn ngữ"
"telegramBotLanguage" = "Ngôn ngữ của Bot Telegram"
[pages.xray] [pages.xray]
"title" = "Cài đặt Xray" "title" = "Cài đặt Xray"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không" "dnsLogDesc" = "Có bật nhật ký truy vấn DNS không"
"maskAddress" = "Ẩn Địa Chỉ" "maskAddress" = "Ẩn Địa Chỉ"
"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký." "maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký."
"statistics" = "Thống kê"
"statsInboundUplink" = "Thống kê tải lên đầu vào"
"statsInboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu vào."
"statsInboundDownlink" = "Thống kê tải xuống đầu vào"
"statsInboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu vào."
"statsOutboundUplink" = "Thống kê tải lên đầu ra"
"statsOutboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu ra."
"statsOutboundDownlink" = "Thống kê tải xuống đầu ra"
"statsOutboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu ra."
[pages.xray.rules] [pages.xray.rules]
"first" = "Đầu tiên" "first" = "Đầu tiên"
@@ -426,6 +453,14 @@
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp" "enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
"tag" = "Thẻ gửi đến DNS" "tag" = "Thẻ gửi đến DNS"
"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến." "tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến."
"clientIp" = "IP khách hàng"
"clientIpDesc" = "Được sử dụng để thông báo cho máy chủ về vị trí IP được chỉ định trong các truy vấn DNS"
"disableCache" = "Tắt bộ nhớ đệm"
"disableCacheDesc" = "Tắt bộ nhớ đệm DNS"
"disableFallback" = "Tắt Fallback"
"disableFallbackDesc" = "Tắt các truy vấn DNS Fallback"
"disableFallbackIfMatch" = "Tắt Fallback Nếu Khớp"
"disableFallbackIfMatchDesc" = "Tắt các truy vấn DNS Fallback khi danh sách tên miền khớp của máy chủ DNS được kích hoạt"
"strategy" = "Chiến lược truy vấn" "strategy" = "Chiến lược truy vấn"
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền" "strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
"add" = "Thêm máy chủ" "add" = "Thêm máy chủ"
@@ -440,7 +475,7 @@
"poolSize" = "Kích thước bể bơi" "poolSize" = "Kích thước bể bơi"
[pages.settings.security] [pages.settings.security]
"admin" = "Quản trị viên" "admin" = "Thông tin đăng nhập quản trị viên"
"secret" = "Mã thông báo bí mật" "secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập" "loginSecurity" = "Bảo mật đăng nhập"
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng" "loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
@@ -483,9 +518,9 @@
"status" = "✅ Bot hoạt động bình thường!" "status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!" "usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>" "getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n<code>/restart force</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>" "helpAdminCommands" = "Để khởi động lại Xray Core:\r\n<code>/restart</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
"helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n<code>/usage [Email]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>" "helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n<code>/usage [Email]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Hoạt động thành công!" "restartSuccess" = "✅ Hoạt động thành công!"
"restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\n<code>Lỗi: {{ .Error }}</code>." "restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\n<code>Lỗi: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core không chạy." "xrayNotRunning" = "❗ Xray Core không chạy."

View File

@@ -44,6 +44,7 @@
"monitor" = "监听" "monitor" = "监听"
"certificate" = "数字证书" "certificate" = "数字证书"
"fail" = "失败" "fail" = "失败"
"comment" = "评论"
"success" = "成功" "success" = "成功"
"getVersion" = "获取版本" "getVersion" = "获取版本"
"install" = "安装" "install" = "安装"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
"emptyDnsDesc" = "未添加DNS服务器。"
"emptyFakeDnsDesc" = "未添加Fake DNS服务器。"
"emptyBalancersDesc" = "未添加负载均衡器。"
"emptyReverseDesc" = "未添加反向代理。"
[menu] [menu]
"dashboard" = "系统状态" "dashboard" = "系统状态"
@@ -108,9 +113,10 @@
"config" = "配置" "config" = "配置"
"backup" = "备份和恢复" "backup" = "备份和恢复"
"backupTitle" = "备份和恢复数据库" "backupTitle" = "备份和恢复数据库"
"backupDescription" = "恢复数据库之前建议进行备份"
"exportDatabase" = "备份" "exportDatabase" = "备份"
"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。"
"importDatabase" = "恢复" "importDatabase" = "恢复"
"importDatabaseDesc" = "点击选择并上传设备中的 .db 文件以从备份恢复数据库。"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息" "subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径" "subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径"
"externalTrafficInformEnable" = "外部交通通知"
"externalTrafficInformEnableDesc" = "每次流量更新时通知外部 API"
"externalTrafficInformURI" = "外部流量通知 URI"
"externalTrafficInformURIDesc" = "流量更新将发送到此 URI"
"fragment" = "分片" "fragment" = "分片"
"fragmentDesc" = "启用 TLS hello 数据包分片" "fragmentDesc" = "启用 TLS hello 数据包分片"
"fragmentSett" = "设置" "fragmentSett" = "设置"
@@ -316,7 +326,15 @@
"muxSett" = "复用器设置" "muxSett" = "复用器设置"
"direct" = "直接连接" "direct" = "直接连接"
"directDesc" = "直接与特定国家的域或IP范围建立连接" "directDesc" = "直接与特定国家的域或IP范围建立连接"
"notifications" = "通知"
"certs" = "证书"
"externalTraffic" = "外部流量"
"dateAndTime" = "日期和时间"
"proxyAndServer" = "代理和服务器"
"intervals" = "间隔"
"information" = "信息"
"language" = "语言"
"telegramBotLanguage" = "Telegram 机器人语言"
[pages.xray] [pages.xray]
"title" = "Xray 配置" "title" = "Xray 配置"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "是否启用 DNS 查询日志" "dnsLogDesc" = "是否启用 DNS 查询日志"
"maskAddress" = "隐藏地址" "maskAddress" = "隐藏地址"
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。" "maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
"statistics" = "统计"
"statsInboundUplink" = "入站上传统计"
"statsInboundUplinkDesc" = "启用所有入站代理的上行流量统计收集。"
"statsInboundDownlink" = "入站下载统计"
"statsInboundDownlinkDesc" = "启用所有入站代理的下行流量统计收集。"
"statsOutboundUplink" = "出站上传统计"
"statsOutboundUplinkDesc" = "启用所有出站代理的上行流量统计收集。"
"statsOutboundDownlink" = "出站下载统计"
"statsOutboundDownlinkDesc" = "启用所有出站代理的下行流量统计收集。"
[pages.xray.rules] [pages.xray.rules]
"first" = "置顶" "first" = "置顶"
@@ -426,6 +453,14 @@
"enableDesc" = "启用内置 DNS 服务器" "enableDesc" = "启用内置 DNS 服务器"
"tag" = "DNS 入站标签" "tag" = "DNS 入站标签"
"tagDesc" = "此标签将在路由规则中可用作入站标签" "tagDesc" = "此标签将在路由规则中可用作入站标签"
"clientIp" = "客户端IP"
"clientIpDesc" = "用于在DNS查询期间通知服务器指定的IP位置"
"disableCache" = "禁用缓存"
"disableCacheDesc" = "禁用DNS缓存"
"disableFallback" = "禁用回退"
"disableFallbackDesc" = "禁用回退DNS查询"
"disableFallbackIfMatch" = "匹配时禁用回退"
"disableFallbackIfMatchDesc" = "当DNS服务器的匹配域名列表命中时禁用回退DNS查询"
"strategy" = "查询策略" "strategy" = "查询策略"
"strategyDesc" = "解析域名的总体策略" "strategyDesc" = "解析域名的总体策略"
"add" = "添加服务器" "add" = "添加服务器"
@@ -440,7 +475,7 @@
"poolSize" = "池大小" "poolSize" = "池大小"
[pages.settings.security] [pages.settings.security]
"admin" = "管理员" "admin" = "管理员凭据"
"secret" = "安全令牌" "secret" = "安全令牌"
"loginSecurity" = "登录安全" "loginSecurity" = "登录安全"
"loginSecurityDesc" = "添加额外的身份验证以提高安全性" "loginSecurityDesc" = "添加额外的身份验证以提高安全性"
@@ -483,9 +518,9 @@
"status" = "✅ 机器人正常运行!" "status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!" "usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>" "getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>"
"helpAdminCommands" = "要重新启动 Xray Core\r\n<code>/restart force</code>\r\n\r\n要搜索客户电子邮件\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站带有客户统计数据\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>" "helpAdminCommands" = "要重新启动 Xray Core\r\n<code>/restart</code>\r\n\r\n要搜索客户电子邮件\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站带有客户统计数据\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>" "helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>." "restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未运行。" "xrayNotRunning" = "❗ Xray Core 未运行。"

View File

@@ -44,6 +44,7 @@
"monitor" = "監聽" "monitor" = "監聽"
"certificate" = "憑證" "certificate" = "憑證"
"fail" = "失敗" "fail" = "失敗"
"comment" = "評論"
"success" = "成功" "success" = "成功"
"getVersion" = "獲取版本" "getVersion" = "獲取版本"
"install" = "安裝" "install" = "安裝"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"emptyDnsDesc" = "未添加DNS伺服器。"
"emptyFakeDnsDesc" = "未添加Fake DNS伺服器。"
"emptyBalancersDesc" = "未添加負載平衡器。"
"emptyReverseDesc" = "未添加反向代理。"
[menu] [menu]
"dashboard" = "系統狀態" "dashboard" = "系統狀態"
@@ -108,9 +113,10 @@
"config" = "配置" "config" = "配置"
"backup" = "備份和恢復" "backup" = "備份和恢復"
"backupTitle" = "備份和恢復資料庫" "backupTitle" = "備份和恢復資料庫"
"backupDescription" = "恢復資料庫之前建議進行備份"
"exportDatabase" = "備份" "exportDatabase" = "備份"
"exportDatabaseDesc" = "點擊下載包含當前資料庫備份的 .db 文件到您的設備。"
"importDatabase" = "恢復" "importDatabase" = "恢復"
"importDatabaseDesc" = "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊" "subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑" "subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑"
"externalTrafficInformEnable" = "外部交通通知"
"externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API"
"externalTrafficInformURI" = "外部流量通知 URI"
"externalTrafficInformURIDesc" = "流量更新將會傳送到此 URI"
"fragment" = "分片" "fragment" = "分片"
"fragmentDesc" = "啟用 TLS hello 資料包分片" "fragmentDesc" = "啟用 TLS hello 資料包分片"
"fragmentSett" = "設定" "fragmentSett" = "設定"
@@ -316,7 +326,15 @@
"muxSett" = "複用器設定" "muxSett" = "複用器設定"
"direct" = "直接連線" "direct" = "直接連線"
"directDesc" = "直接與特定國家的域或IP範圍建立連線" "directDesc" = "直接與特定國家的域或IP範圍建立連線"
"notifications" = "通知"
"certs" = "證書"
"externalTraffic" = "外部流量"
"dateAndTime" = "日期和時間"
"proxyAndServer" = "代理和伺服器"
"intervals" = "間隔"
"information" = "資訊"
"language" = "語言"
"telegramBotLanguage" = "Telegram 機器人語言"
[pages.xray] [pages.xray]
"title" = "Xray 配置" "title" = "Xray 配置"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "是否啟用 DNS 查詢日誌" "dnsLogDesc" = "是否啟用 DNS 查詢日誌"
"maskAddress" = "隱藏地址" "maskAddress" = "隱藏地址"
"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。" "maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。"
"statistics" = "統計"
"statsInboundUplink" = "入站上傳統計"
"statsInboundUplinkDesc" = "啟用所有入站代理的上行流量統計收集。"
"statsInboundDownlink" = "入站下載統計"
"statsInboundDownlinkDesc" = "啟用所有入站代理的下行流量統計收集。"
"statsOutboundUplink" = "出站上傳統計"
"statsOutboundUplinkDesc" = "啟用所有出站代理的上行流量統計收集。"
"statsOutboundDownlink" = "出站下載統計"
"statsOutboundDownlinkDesc" = "啟用所有出站代理的下行流量統計收集。"
[pages.xray.rules] [pages.xray.rules]
"first" = "置頂" "first" = "置頂"
@@ -426,6 +453,14 @@
"enableDesc" = "啟用內建 DNS 伺服器" "enableDesc" = "啟用內建 DNS 伺服器"
"tag" = "DNS 入站標籤" "tag" = "DNS 入站標籤"
"tagDesc" = "此標籤將在路由規則中可用作入站標籤" "tagDesc" = "此標籤將在路由規則中可用作入站標籤"
"clientIp" = "客戶端IP"
"clientIpDesc" = "用於在DNS查詢期間通知伺服器指定的IP位置"
"disableCache" = "禁用快取"
"disableCacheDesc" = "禁用DNS快取"
"disableFallback" = "禁用回退"
"disableFallbackDesc" = "禁用回退DNS查詢"
"disableFallbackIfMatch" = "匹配時禁用回退"
"disableFallbackIfMatchDesc" = "當DNS伺服器的匹配域名列表命中時禁用回退DNS查詢"
"strategy" = "查詢策略" "strategy" = "查詢策略"
"strategyDesc" = "解析域名的總體策略" "strategyDesc" = "解析域名的總體策略"
"add" = "新增伺服器" "add" = "新增伺服器"
@@ -440,7 +475,7 @@
"poolSize" = "池大小" "poolSize" = "池大小"
[pages.settings.security] [pages.settings.security]
"admin" = "管理員" "admin" = "管理員憑證"
"secret" = "安全令牌" "secret" = "安全令牌"
"loginSecurity" = "登入安全" "loginSecurity" = "登入安全"
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性" "loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
@@ -483,9 +518,9 @@
"status" = "✅ 機器人正常執行!" "status" = "✅ 機器人正常執行!"
"usage" = "❗ 請輸入要搜尋的文字!" "usage" = "❗ 請輸入要搜尋的文字!"
"getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>" "getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>"
"helpAdminCommands" = "要重新啟動 Xray Core\r\n<code>/restart force</code>\r\n\r\n要搜尋客戶電子郵件\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站帶有客戶統計資料\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>" "helpAdminCommands" = "要重新啟動 Xray Core\r\n<code>/restart</code>\r\n\r\n要搜尋客戶電子郵件\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站帶有客戶統計資料\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>" "helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram聊天ID\r\n<code>/id</code>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>" "restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>." "restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未運行。" "xrayNotRunning" = "❗ Xray Core 未運行。"

362
x-ui.sh
View File

@@ -2,6 +2,7 @@
red='\033[0;31m' red='\033[0;31m'
green='\033[0;32m' green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m' yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
@@ -86,6 +87,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "virtuozzo" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Virtuozzo Linux 8 or higher ${plain}\n" && exit 1
fi
else else
echo -e "${red}Your operating system is not supported by this script.${plain}\n" echo -e "${red}Your operating system is not supported by this script.${plain}\n"
echo "Please ensure you are using one of the following supported operating systems:" echo "Please ensure you are using one of the following supported operating systems:"
@@ -103,6 +108,7 @@ else
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed" echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023" echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1 exit 1
fi fi
@@ -179,7 +185,7 @@ update_menu() {
return 0 return 0
fi fi
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
@@ -279,7 +285,7 @@ reset_webbasepath() {
# Apply the new web base path setting # Apply the new web base path setting
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1 /usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}" echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
echo -e "${green}Please use the new web base path to access the panel.${plain}" echo -e "${green}Please use the new web base path to access the panel.${plain}"
restart restart
@@ -439,7 +445,7 @@ show_log() {
1) 1)
journalctl -u x-ui -e --no-pager -f -p debug journalctl -u x-ui -e --no-pager -f -p debug
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
before_show_menu before_show_menu
fi fi
;; ;;
2) 2)
@@ -457,9 +463,9 @@ show_log() {
show_banlog() { show_banlog() {
local system_log="/var/log/fail2ban.log" local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n" echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n" echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1 return 1
@@ -503,7 +509,7 @@ bbr_menu() {
disable_bbr disable_bbr
bbr_menu bbr_menu
;; ;;
*) *)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
bbr_menu bbr_menu
;; ;;
@@ -546,7 +552,7 @@ enable_bbr() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum -y install ca-certificates yum -y update && yum -y install ca-certificates
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install ca-certificates dnf -y update && dnf -y install ca-certificates
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -574,14 +580,14 @@ enable_bbr() {
} }
update_shell() { update_shell() {
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh wget -O /usr/bin/x-ui -N https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo "" echo ""
LOGE "Failed to download script, Please check whether the machine can connect Github" LOGE "Failed to download script, Please check whether the machine can connect Github"
before_show_menu before_show_menu
else else
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
LOGI "Upgrade script succeeded, Please rerun the script" LOGI "Upgrade script succeeded, Please rerun the script"
before_show_menu before_show_menu
fi fi
} }
@@ -682,10 +688,13 @@ show_xray_status() {
} }
firewall_menu() { firewall_menu() {
echo -e "${green}\t1.${plain} Install Firewall & open ports" echo -e "${green}\t1.${plain} ${green}Install${plain} Firewall"
echo -e "${green}\t2.${plain} Allowed List" echo -e "${green}\t2.${plain} Port List [numbered]"
echo -e "${green}\t3.${plain} Delete Ports from List" echo -e "${green}\t3.${plain} ${green}Open${plain} Ports"
echo -e "${green}\t4.${plain} Disable Firewall" echo -e "${green}\t4.${plain} ${red}Delete${plain} Ports from List"
echo -e "${green}\t5.${plain} ${green}Enable${plain} Firewall"
echo -e "${green}\t6.${plain} ${red}Disable${plain} Firewall"
echo -e "${green}\t7.${plain} Firewall Status"
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
@@ -693,29 +702,41 @@ firewall_menu() {
show_menu show_menu
;; ;;
1) 1)
open_ports install_firewall
firewall_menu firewall_menu
;; ;;
2) 2)
sudo ufw status ufw status numbered
firewall_menu firewall_menu
;; ;;
3) 3)
delete_ports open_ports
firewall_menu firewall_menu
;; ;;
4) 4)
sudo ufw disable delete_ports
firewall_menu firewall_menu
;; ;;
*) 5)
ufw enable
firewall_menu
;;
6)
ufw disable
firewall_menu
;;
7)
ufw status verbose
firewall_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
firewall_menu firewall_menu
;; ;;
esac esac
} }
open_ports() { install_firewall() {
if ! command -v ufw &>/dev/null; then if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..." echo "ufw firewall is not installed. Installing now..."
apt-get update apt-get update
@@ -733,13 +754,16 @@ open_ports() {
ufw allow ssh ufw allow ssh
ufw allow http ufw allow http
ufw allow https ufw allow https
ufw allow 2053/tcp ufw allow 2053/tcp #webPort
ufw allow 2096/tcp #subport
# Enable the firewall # Enable the firewall
ufw --force enable ufw --force enable
fi fi
}
# Prompt the user to enter a list of ports open_ports() {
# Prompt the user to enter the ports they want to open
read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports read -p "Enter the ports you want to open (e.g. 80,443,2053 or range 400-500): " ports
# Check if the input is valid # Check if the input is valid
@@ -755,66 +779,109 @@ open_ports() {
# Split the range into start and end ports # Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1) start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2) end_port=$(echo $port | cut -d'-' -f2)
# Open the port range
ufw allow $start_port:$end_port/tcp ufw allow $start_port:$end_port/tcp
ufw allow $start_port:$end_port/udp ufw allow $start_port:$end_port/udp
else else
# Open the single port
ufw allow "$port" ufw allow "$port"
fi fi
done done
# Confirm that the ports are open # Confirm that the ports are opened
echo "The following ports are now open:" echo "Opened the specified ports:"
ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?" for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
echo "Firewall status:" start_port=$(echo $port | cut -d'-' -f1)
ufw status verbose end_port=$(echo $port | cut -d'-' -f2)
# Check if the port range has been successfully opened
(ufw status | grep -q "$start_port:$end_port") && echo "$start_port-$end_port"
else
# Check if the individual port has been successfully opened
(ufw status | grep -q "$port") && echo "$port"
fi
done
} }
delete_ports() { delete_ports() {
# Prompt the user to enter the ports they want to delete # Display current rules with numbers
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports echo "Current UFW rules:"
ufw status numbered
# Check if the input is valid # Ask the user how they want to delete rules
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then echo "Do you want to delete rules by:"
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2 echo "1) Rule numbers"
echo "2) Ports"
read -p "Enter your choice (1 or 2): " choice
if [[ $choice -eq 1 ]]; then
# Deleting by rule numbers
read -p "Enter the rule numbers you want to delete (1, 2, etc.): " rule_numbers
# Validate the input
if ! [[ $rule_numbers =~ ^([0-9]+)(,[0-9]+)*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of rule numbers." >&2
exit 1
fi
# Split numbers into an array
IFS=',' read -ra RULE_NUMBERS <<<"$rule_numbers"
for rule_number in "${RULE_NUMBERS[@]}"; do
# Delete the rule by number
ufw delete "$rule_number" || echo "Failed to delete rule number $rule_number"
done
echo "Selected rules have been deleted."
elif [[ $choice -eq 2 ]]; then
# Deleting by ports
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
# Validate the input
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
exit 1
fi
# Split ports into an array
IFS=',' read -ra PORT_LIST <<<"$ports"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
# Split the port range
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Delete the port range
ufw delete allow $start_port:$end_port/tcp
ufw delete allow $start_port:$end_port/udp
else
# Delete a single port
ufw delete allow "$port"
fi
done
# Confirmation of deletion
echo "Deleted the specified ports:"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Check if the port range has been deleted
(ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
else
# Check if the individual port has been deleted
(ufw status | grep -q "$port") || echo "$port"
fi
done
else
echo "${red}Error:${plain} Invalid choice. Please enter 1 or 2." >&2
exit 1 exit 1
fi fi
# Delete the specified ports using ufw
IFS=',' read -ra PORT_LIST <<<"$ports"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Delete the port range
ufw delete allow $start_port:$end_port/tcp
ufw delete allow $start_port:$end_port/udp
else
ufw delete allow "$port"
fi
done
# Confirm that the ports are deleted
echo "Deleted the specified ports:"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Check if the port range has been successfully deleted
(ufw status | grep -q "$start_port:$end_port") || echo "$start_port-$end_port"
else
# Check if the individual port has been successfully deleted
(ufw status | grep -q "$port") || echo "$port"
fi
done
} }
update_geo() { update_geo() {
echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)" echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)"
echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)" echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)"
echo -e "${green}\t3.${plain} vuong2023 (geoip_VN.dat, geosite_VN.dat)" echo -e "${green}\t3.${plain} runetfreedom (geoip_RU.dat, geosite_RU.dat)"
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
@@ -842,10 +909,10 @@ update_geo() {
;; ;;
3) 3)
systemctl stop x-ui systemctl stop x-ui
rm -f geoip_VN.dat geosite_VN.dat rm -f geoip_RU.dat geosite_RU.dat
wget -O geoip_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -O geosite_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
echo -e "${green}vuong2023 datasets have been updated successfully!${plain}" echo -e "${green}runetfreedom datasets have been updated successfully!${plain}"
restart restart
;; ;;
*) *)
@@ -1006,7 +1073,7 @@ ssl_cert_issue() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum -y install socat yum -y update && yum -y install socat
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install socat dnf -y update && dnf -y install socat
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -1278,8 +1345,8 @@ run_speedtest() {
} }
create_iplimit_jails() { create_iplimit_jails() {
# Use default bantime if not passed => 15 minutes # Use default bantime if not passed => 30 minutes
local bantime="${1:-15}" local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf # Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
@@ -1310,7 +1377,7 @@ EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES] [INCLUDES]
before = iptables-common.conf before = iptables-allports.conf
[Definition] [Definition]
actionstart = <iptables> -N f2b-<name> actionstart = <iptables> -N f2b-<name>
@@ -1353,15 +1420,22 @@ iplimit_remove_conflicts() {
done done
} }
ip_validation() {
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)$"
}
iplimit_main() { iplimit_main() {
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit" echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
echo -e "${green}\t2.${plain} Change Ban Duration" echo -e "${green}\t2.${plain} Change Ban Duration"
echo -e "${green}\t3.${plain} Unban Everyone" echo -e "${green}\t3.${plain} Unban Everyone"
echo -e "${green}\t4.${plain} Ban Logs" echo -e "${green}\t4.${plain} Ban Logs"
echo -e "${green}\t5.${plain} Real-Time Logs" echo -e "${green}\t5.${plain} Ban an IP Address"
echo -e "${green}\t6.${plain} Service Status" echo -e "${green}\t6.${plain} Unban an IP Address"
echo -e "${green}\t7.${plain} Service Restart" echo -e "${green}\t7.${plain} Real-Time Logs"
echo -e "${green}\t8.${plain} Uninstall Fail2ban and IP Limit" echo -e "${green}\t8.${plain} Service Status"
echo -e "${green}\t9.${plain} Service Restart"
echo -e "${green}\t10.${plain} Uninstall Fail2ban and IP Limit"
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
@@ -1403,22 +1477,44 @@ iplimit_main() {
iplimit_main iplimit_main
;; ;;
5) 5)
tail -f /var/log/fail2ban.log read -rp "Enter the IP address you want to ban: " ban_ip
ip_validation
if [[ $ban_ip =~ $ipv4_regex || $ban_ip =~ $ipv6_regex ]]; then
fail2ban-client set 3x-ipl banip "$ban_ip"
echo -e "${green}IP Address ${ban_ip} has been banned successfully.${plain}"
else
echo -e "${red}Invalid IP address format! Please try again.${plain}"
fi
iplimit_main iplimit_main
;; ;;
6) 6)
service fail2ban status read -rp "Enter the IP address you want to unban: " unban_ip
ip_validation
if [[ $unban_ip =~ $ipv4_regex || $unban_ip =~ $ipv6_regex ]]; then
fail2ban-client set 3x-ipl unbanip "$unban_ip"
echo -e "${green}IP Address ${unban_ip} has been unbanned successfully.${plain}"
else
echo -e "${red}Invalid IP address format! Please try again.${plain}"
fi
iplimit_main iplimit_main
;; ;;
7) 7)
systemctl restart fail2ban tail -f /var/log/fail2ban.log
iplimit_main iplimit_main
;; ;;
8) 8)
service fail2ban status
iplimit_main
;;
9)
systemctl restart fail2ban
iplimit_main
;;
10)
remove_iplimit remove_iplimit
iplimit_main iplimit_main
;; ;;
*) *)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
iplimit_main iplimit_main
;; ;;
@@ -1445,7 +1541,7 @@ install_iplimit() {
yum update -y && yum install epel-release -y yum update -y && yum install epel-release -y
yum -y install fail2ban yum -y install fail2ban
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install fail2ban dnf -y update && dnf -y install fail2ban
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -1526,7 +1622,7 @@ remove_iplimit() {
yum remove fail2ban -y yum remove fail2ban -y
yum autoremove -y yum autoremove -y
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf remove fail2ban -y dnf remove fail2ban -y
dnf autoremove -y dnf autoremove -y
;; ;;
@@ -1629,61 +1725,63 @@ SSH_port_forwarding() {
} }
show_usage() { show_usage() {
echo "x-ui control menu usages: " echo -e "┌───────────────────────────────────────────────────────┐
echo "------------------------------------------" ${blue}x-ui control menu usages (subcommands):${plain}
echo -e "SUBCOMMANDS:" │ │
echo -e "x-ui - Admin Management Script" ${blue}x-ui${plain} - Admin Management Script
echo -e "x-ui start - Start" ${blue}x-ui start${plain} - Start │
echo -e "x-ui stop - Stop" ${blue}x-ui stop${plain} - Stop │
echo -e "x-ui restart - Restart" ${blue}x-ui restart${plain} - Restart │
echo -e "x-ui status - Current Status" ${blue}x-ui status${plain} - Current Status
echo -e "x-ui settings - Current Settings" ${blue}x-ui settings${plain} - Current Settings
echo -e "x-ui enable - Enable Autostart on OS Startup" ${blue}x-ui enable${plain} - Enable Autostart on OS Startup
echo -e "x-ui disable - Disable Autostart on OS Startup" ${blue}x-ui disable${plain} - Disable Autostart on OS Startup
echo -e "x-ui log - Check logs" ${blue}x-ui log${plain} - Check logs │
echo -e "x-ui banlog - Check Fail2ban ban logs" ${blue}x-ui banlog${plain} - Check Fail2ban ban logs
echo -e "x-ui update - Update" ${blue}x-ui update${plain} - Update │
echo -e "x-ui custom - custom version" ${blue}x-ui legacy${plain} - legacy version
echo -e "x-ui install - Install" ${blue}x-ui install${plain} - Install │
echo -e "x-ui uninstall - Uninstall" ${blue}x-ui uninstall${plain} - Uninstall │
echo "------------------------------------------" └───────────────────────────────────────────────────────┘"
} }
show_menu() { show_menu() {
echo -e " echo -e "
${green}3X-UI Panel Management Script${plain} ╔────────────────────────────────────────────────╗
${green}0.${plain} Exit Script ${green}3X-UI Panel Management Script${plain}
———————————————— ${green}0.${plain} Exit Script │
${green}1.${plain} Install │────────────────────────────────────────────────│
${green}2.${plain} Update ${green}1.${plain} Install │
${green}3.${plain} Update Menu ${green}2.${plain} Update
${green}4.${plain} Legacy Version ${green}3.${plain} Update Menu │
${green}5.${plain} Uninstall ${green}4.${plain} Legacy Version │
———————————————— ${green}5.${plain} Uninstall │
${green}6.${plain} Reset Username & Password & Secret Token │────────────────────────────────────────────────│
${green}7.${plain} Reset Web Base Path ${green}6.${plain} Reset Username & Password & Secret Token │
${green}8.${plain} Reset Settings ${green}7.${plain} Reset Web Base Path │
${green}9.${plain} Change Port ${green}8.${plain} Reset Settings │
${green}10.${plain} View Current Settings ${green}9.${plain} Change Port │
———————————————— ${green}10.${plain} View Current Settings │
${green}11.${plain} Start │────────────────────────────────────────────────│
${green}12.${plain} Stop ${green}11.${plain} Start │
${green}13.${plain} Restart ${green}12.${plain} Stop │
${green}14.${plain} Check Status ${green}13.${plain} Restart │
${green}15.${plain} Logs Management ${green}14.${plain} Check Status │
———————————————— ${green}15.${plain} Logs Management │
${green}16.${plain} Enable Autostart │────────────────────────────────────────────────│
${green}17.${plain} Disable Autostart ${green}16.${plain} Enable Autostart
———————————————— ${green}17.${plain} Disable Autostart │
${green}18.${plain} SSL Certificate Management │────────────────────────────────────────────────│
${green}19.${plain} Cloudflare SSL Certificate ${green}18.${plain} SSL Certificate Management │
${green}20.${plain} IP Limit Management ${green}19.${plain} Cloudflare SSL Certificate │
${green}21.${plain} Firewall Management ${green}20.${plain} IP Limit Management
${green}22.${plain} SSH Port Forwarding Management ${green}21.${plain} Firewall Management │
———————————————— ${green}22.${plain} SSH Port Forwarding Management │
${green}23.${plain} Enable BBR │────────────────────────────────────────────────│
${green}24.${plain} Update Geo Files ${green}23.${plain} Enable BBR │
${green}25.${plain} Speedtest by Ookla ${green}24.${plain} Update Geo Files │
${green}25.${plain} Speedtest by Ookla │
╚────────────────────────────────────────────────╝
" "
show_status show_status
echo && read -p "Please enter your selection [0-25]: " num echo && read -p "Please enter your selection [0-25]: " num

View File

@@ -32,32 +32,39 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
return len(m), nil return len(m), nil
} }
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`) regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
messages := strings.Split(message, "\n") messages := strings.SplitSeq(message, "\n")
for _, msg := range messages { for msg := range messages {
matches := regex.FindStringSubmatch(msg) matches := regex.FindStringSubmatch(msg)
if len(matches) > 3 { if len(matches) > 3 {
level := matches[2] level := matches[2]
msgBody := matches[3] msgBody := matches[3]
// Map the level to the appropriate logger function if strings.Contains(strings.ToLower(msgBody), "failed") {
switch level {
case "Debug":
logger.Debug("XRAY: " + msgBody)
case "Info":
logger.Info("XRAY: " + msgBody)
case "Warning":
logger.Warning("XRAY: " + msgBody)
case "Error":
logger.Error("XRAY: " + msgBody) logger.Error("XRAY: " + msgBody)
default: } else {
logger.Debug("XRAY: " + msg) switch level {
case "Debug":
logger.Debug("XRAY: " + msgBody)
case "Info":
logger.Info("XRAY: " + msgBody)
case "Warning":
logger.Warning("XRAY: " + msgBody)
case "Error":
logger.Error("XRAY: " + msgBody)
default:
logger.Debug("XRAY: " + msg)
}
} }
lw.lastLine = "" lw.lastLine = ""
} else if msg != "" { } else if msg != "" {
logger.Debug("XRAY: " + msg) if strings.Contains(strings.ToLower(msg), "failed") {
logger.Error("XRAY: " + msg)
} else {
logger.Debug("XRAY: " + msg)
}
lw.lastLine = msg lw.lastLine = msg
} }
} }