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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
# ========================================================
# Stage: Builder
# ========================================================
FROM golang:1.23-alpine AS builder
FROM golang:1.24-alpine AS builder
WORKDIR /app
ARG TARGETARCH
@@ -15,7 +15,7 @@ COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go
RUN go build -ldflags "-w -s" -o build/x-ui main.go
RUN ./DockerInit.sh "$TARGETARCH"
# ========================================================
@@ -48,6 +48,7 @@ RUN chmod +x \
/app/x-ui \
/usr/bin/x-ui
ENV X_UI_ENABLE_FAIL2BAN="true"
VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ]
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">
<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">
</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>
## Un agradecimiento especial a
@@ -579,8 +580,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## 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._
- [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
[![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">
<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">
</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>
## A Special Thanks to
@@ -588,8 +589,8 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## 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._
- [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](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">
<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">
</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>
## Особая благодарность
@@ -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._
- [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">
<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">
</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>
## 特别感谢
@@ -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._
- [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趋势
[![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"`
TgID int64 `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"`
Comment string `json:"comment" form:"comment"`
Reset int `json:"reset" form:"reset"`
}

View File

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

89
go.mod
View File

@@ -1,46 +1,45 @@
module x-ui
go 1.23.4
go 1.24.1
require (
github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/sessions v1.0.1
github.com/gin-contrib/gzip v1.2.2
github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.4
github.com/mymmrac/telego v0.31.4
github.com/nicksnyder/go-i18n/v2 v2.4.1
github.com/goccy/go-json v0.10.5
github.com/mymmrac/telego v0.32.0
github.com/nicksnyder/go-i18n/v2 v2.5.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.3
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.24.11
github.com/valyala/fasthttp v1.58.0
github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4
github.com/shirou/gopsutil/v4 v4.25.2
github.com/valyala/fasthttp v1.59.0
github.com/xtls/xray-core v1.250306.0
go.uber.org/atomic v1.11.0
golang.org/x/text v0.21.0
google.golang.org/grpc v1.69.0
golang.org/x/text v0.23.0
google.golang.org/grpc v1.71.0
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12
)
require (
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/fasthttp/router v1.5.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/fasthttp/router v1.5.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.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/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/securecookie v1.1.2 // 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/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.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-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
github.com/onsi/ginkgo/v2 v2.23.0 // indirect
github.com/pires/go-proxyproto v0.8.0 // 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/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/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sagernet/sing v0.5.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.6.3 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
@@ -83,20 +82,20 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.28.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.31.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/protobuf v1.35.2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // 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/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
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.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
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.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fasthttp/router v1.5.3 h1:BFWXqa3e4thRI3MgPKTNtz0Oiq6UYN2OsEtb+YQ5TMI=
github.com/fasthttp/router v1.5.3/go.mod h1:b864KkDIapOYh77AVG/SNkwfRZ6k6ecWvD+ZRXmP5pw=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
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/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
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/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
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/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
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.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
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.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
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.31.4/go.mod h1:T12js1PgbYDYznvoN05MSMuPMfWTYo7D9LKl5cPFWiI=
github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g=
github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
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/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
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/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/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
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/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE=
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/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/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/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8=
github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
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.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
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/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
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.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
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/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
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/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/xray-core v1.8.25-0.20241215123619-7d0a80b501d4 h1:zdd86FEjFZjAaRbWxiZQM2QPOzk/d6cig2DaE7c3MDQ=
github.com/xtls/xray-core v1.8.25-0.20241215123619-7d0a80b501d4/go.mod h1:lduNPDkXku+Avphl8g7W0yJrHhWyxdOnPo0XGYdF0Aw=
github.com/xtls/xray-core v1.250306.0 h1:XZyZvSgcpAoVEGnFnxNdoHbSF7Kp77A/0TPk4lhv6rM=
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/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
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/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
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/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.6.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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
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/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
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-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -2,6 +2,7 @@
red='\033[0;31m'
green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m'
plain='\033[0m'
@@ -89,6 +90,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
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
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:"
@@ -106,6 +111,7 @@ else
echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1
fi
@@ -117,7 +123,7 @@ install_base() {
centos | almalinux | rocky | ol)
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
;;
arch | manjaro | parch)
@@ -208,7 +214,7 @@ install_x-ui() {
exit 1
fi
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
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
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"
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
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1
@@ -250,7 +256,7 @@ install_x-ui() {
chmod +x x-ui bin/xray-linux-$(arch)
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/bin/x-ui
config_after_install
@@ -260,24 +266,24 @@ install_x-ui() {
systemctl start x-ui
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
echo -e ""
echo -e "x-ui control menu usages: "
echo -e "----------------------------------------------"
echo -e "SUBCOMMANDS:"
echo -e "x-ui - Admin Management Script"
echo -e "x-ui start - Start"
echo -e "x-ui stop - Stop"
echo -e "x-ui restart - Restart"
echo -e "x-ui status - Current Status"
echo -e "x-ui settings - Current Settings"
echo -e "x-ui enable - Enable Autostart on OS Startup"
echo -e "x-ui disable - Disable Autostart on OS Startup"
echo -e "x-ui log - Check logs"
echo -e "x-ui banlog - Check Fail2ban ban logs"
echo -e "x-ui update - Update"
echo -e "x-ui legacy - legacy version"
echo -e "x-ui install - Install"
echo -e "x-ui uninstall - Uninstall"
echo -e "----------------------------------------------"
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
│ │
${blue}x-ui${plain} - Admin Management Script
${blue}x-ui start${plain} - Start │
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status
${blue}x-ui settings${plain} - Current Settings
${blue}x-ui enable${plain} - Enable Autostart on OS Startup
${blue}x-ui disable${plain} - Disable Autostart on OS Startup
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
}
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{}
defaultOutbounds []json_util.RawMessage
fragment string
noises string
noises string
mux string
inboundService service.InboundService
@@ -61,7 +61,7 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
noises: noises,
noises: noises,
mux: mux,
SubService: subService,
}
@@ -217,7 +217,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
delete(streamSettings, "sockopt")
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

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() {
return toFixed(this.total / ONE_GB, 2);
return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
}
set totalGB(gb) {
this.total = toFixed(gb * ONE_GB, 0);
this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
}
get isVMess() {

View File

@@ -34,10 +34,6 @@ const TLS_VERSION_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_256_GCM: "TLS_AES_256_GCM_SHA384",
CHACHA20_POLY1305: "TLS_CHACHA20_POLY1305_SHA256",
@@ -64,6 +60,8 @@ const UTLS_FINGERPRINT = {
UTLS_QQ: "qq",
UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe",
};
const ALPN_OPTION = {
@@ -496,19 +494,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
headers = [],
scMaxBufferedPosts = 30,
scMaxEachPostBytes = "1000000",
scMinPostsIntervalMs = "30",
scStreamUpServerSecs = "20-80",
noSSEHeader = false,
xPaddingBytes = "100-1000",
xmux = {
maxConcurrency: "16-32",
maxConnections: 0,
cMaxReuseTimes: "64-128",
cMaxLifetimeMs: 0,
hMaxRequestTimes: "800-900",
hKeepAlivePeriod: 0,
},
mode = MODE_OPTION.AUTO,
noGRPCHeader = false
) {
super();
this.path = path;
@@ -516,12 +505,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
this.headers = headers;
this.scMaxBufferedPosts = scMaxBufferedPosts;
this.scMaxEachPostBytes = scMaxEachPostBytes;
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
this.scStreamUpServerSecs = scStreamUpServerSecs;
this.noSSEHeader = noSSEHeader;
this.xPaddingBytes = xPaddingBytes;
this.xmux = xmux;
this.mode = mode;
this.noGRPCHeader = noGRPCHeader;
}
addHeader(name, value) {
@@ -539,12 +526,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
XrayCommonClass.toHeaders(json.headers),
json.scMaxBufferedPosts,
json.scMaxEachPostBytes,
json.scMinPostsIntervalMs,
json.scStreamUpServerSecs,
json.noSSEHeader,
json.xPaddingBytes,
json.xmux,
json.mode,
json.noGRPCHeader,
);
}
@@ -555,19 +540,10 @@ class xHTTPStreamSettings extends XrayCommonClass {
headers: XrayCommonClass.toV2Headers(this.headers, false),
scMaxBufferedPosts: this.scMaxBufferedPosts,
scMaxEachPostBytes: this.scMaxEachPostBytes,
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
scStreamUpServerSecs: this.scStreamUpServerSecs,
noSSEHeader: this.noSSEHeader,
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,
noGRPCHeader: this.noGRPCHeader,
};
}
}
@@ -579,6 +555,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -591,6 +568,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -622,6 +600,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
json.verifyPeerCertInNames,
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -637,6 +616,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
@@ -718,7 +698,10 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
};
TlsStreamSettings.Settings = class extends XrayCommonClass {
constructor(allowInsecure = false, fingerprint = '') {
constructor(
allowInsecure = false,
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
) {
super();
this.allowInsecure = allowInsecure;
this.fingerprint = fingerprint;
@@ -784,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass {
json.maxClient,
json.maxTimediff,
json.shortIds,
json.settings,
settings,
);
}
@@ -807,7 +790,7 @@ class RealityStreamSettings extends XrayCommonClass {
RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor(
publicKey = '',
fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM,
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
serverName = '',
spiderX = '/'
) {
@@ -842,7 +825,7 @@ class SockoptStreamSettings extends XrayCommonClass {
mark = 0,
tproxy = "off",
tcpMptcp = false,
tcpNoDelay = false,
penetrate = false,
domainStrategy = DOMAIN_STRATEGY_OPTION.USE_IP,
tcpMaxSeg = 1440,
dialerProxy = "",
@@ -860,7 +843,7 @@ class SockoptStreamSettings extends XrayCommonClass {
this.mark = mark;
this.tproxy = tproxy;
this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay;
this.penetrate = penetrate;
this.domainStrategy = domainStrategy;
this.tcpMaxSeg = tcpMaxSeg;
this.dialerProxy = dialerProxy;
@@ -881,7 +864,7 @@ class SockoptStreamSettings extends XrayCommonClass {
json.mark,
json.tproxy,
json.tcpMptcp,
json.tcpNoDelay,
json.penetrate,
json.domainStrategy,
json.tcpMaxSeg,
json.dialerProxy,
@@ -902,7 +885,7 @@ class SockoptStreamSettings extends XrayCommonClass {
mark: this.mark,
tproxy: this.tproxy,
tcpMptcp: this.tcpMptcp,
tcpNoDelay: this.tcpNoDelay,
penetrate: this.penetrate,
domainStrategy: this.domainStrategy,
tcpMaxSeg: this.tcpMaxSeg,
dialerProxy: this.dialerProxy,
@@ -1067,7 +1050,7 @@ class Allocate extends XrayCommonClass {
class Inbound extends XrayCommonClass {
constructor(
port = RandomUtil.randomIntRange(10000, 60000),
port = RandomUtil.randomInteger(10000, 60000),
listen = '',
protocol = Protocols.VLESS,
settings = null,
@@ -1243,7 +1226,7 @@ class Inbound extends XrayCommonClass {
}
reset() {
this.port = RandomUtil.randomIntRange(10000, 60000);
this.port = RandomUtil.randomInteger(10000, 60000);
this.listen = '';
this.protocol = Protocols.VMESS;
this.settings = Inbound.Settings.getSettings(Protocols.VMESS);
@@ -1304,7 +1287,7 @@ class Inbound extends XrayCommonClass {
obj.mode = xhttp.mode;
}
if (security === 'tls') {
if (tls === 'tls') {
if (!ObjectUtil.isEmpty(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) {
@@ -1491,7 +1474,7 @@ class Inbound extends XrayCommonClass {
if (this.isSS2022) password.push(settings.password);
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);
for (const [key, value] of params) {
url.searchParams.set(key, value)
@@ -1804,6 +1787,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
enable = true,
tgId = '',
subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0
) {
super();
@@ -1816,6 +1800,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.comment = comment;
this.reset = reset;
}
@@ -1830,6 +1815,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.comment,
json.reset,
);
}
@@ -1851,11 +1837,11 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
}
}
get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2);
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
}
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,
tgId = '',
subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0
) {
super();
@@ -1922,6 +1909,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.comment = comment;
this.reset = reset;
}
@@ -1936,6 +1924,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.comment,
json.reset,
);
}
@@ -1958,11 +1947,11 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
}
}
get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2);
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
}
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 {
@@ -2046,6 +2035,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable = true,
tgId = '',
subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0
) {
super();
@@ -2057,6 +2047,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.comment = comment;
this.reset = reset;
}
@@ -2070,6 +2061,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
comment: this.comment,
reset: this.reset,
};
}
@@ -2084,6 +2076,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.comment,
json.reset,
);
}
@@ -2106,11 +2099,11 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
}
}
get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2);
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
}
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,
tgId = '',
subId = RandomUtil.randomLowerAndNum(16),
comment = '',
reset = 0
) {
super();
@@ -2215,6 +2209,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.enable = enable;
this.tgId = tgId;
this.subId = subId;
this.comment = comment;
this.reset = reset;
}
@@ -2229,6 +2224,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable: this.enable,
tgId: this.tgId,
subId: this.subId,
comment: this.comment,
reset: this.reset,
};
}
@@ -2244,6 +2240,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.enable,
json.tgId,
json.subId,
json.comment,
json.reset,
);
}
@@ -2266,11 +2263,11 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
}
}
get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2);
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
}
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_RANDOM: "random",
UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe",
};
const ALPN_OPTION = {
@@ -84,6 +86,16 @@ const MODE_OPTION = {
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(SSMethods);
Object.freeze(TLS_FLOW_CONTROL);
@@ -93,7 +105,7 @@ Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
Object.freeze(USERS_SECURITY);
Object.freeze(MODE_OPTION);
Object.freeze(Address_Port_Strategy);
class CommonClass {
@@ -287,11 +299,24 @@ class xHTTPStreamSettings extends CommonClass {
path = '/',
host = '',
mode = '',
noGRPCHeader = false,
scMinPostsIntervalMs = "30",
xmux = {
maxConcurrency: "16-32",
maxConnections: 0,
cMaxReuseTimes: 0,
hMaxRequestTimes: "600-900",
hMaxReusableSecs: "1800-3000",
hKeepAlivePeriod: 0,
},
) {
super();
this.path = path;
this.host = host;
this.mode = mode;
this.noGRPCHeader = noGRPCHeader;
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
this.xmux = xmux;
}
static fromJson(json = {}) {
@@ -299,6 +324,9 @@ class xHTTPStreamSettings extends CommonClass {
json.path,
json.host,
json.mode,
json.noGRPCHeader,
json.scMinPostsIntervalMs,
json.xmux
);
}
@@ -307,6 +335,16 @@ class xHTTPStreamSettings extends CommonClass {
path: this.path,
host: this.host,
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,
tcpKeepAliveInterval = 0,
tcpMptcp = false,
tcpNoDelay = false
penetrate = false,
addressPortStrategy = Address_Port_Strategy.NONE,
) {
super();
this.dialerProxy = dialerProxy;
this.tcpFastOpen = tcpFastOpen;
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpMptcp = tcpMptcp;
this.tcpNoDelay = tcpNoDelay;
this.penetrate = penetrate;
this.addressPortStrategy = addressPortStrategy;
}
static fromJson(json = {}) {
@@ -401,7 +441,8 @@ class SockoptStreamSettings extends CommonClass {
json.tcpFastOpen,
json.tcpKeepAliveInterval,
json.tcpMptcp,
json.tcpNoDelay,
json.penetrate,
json.addressPortStrategy
);
}
@@ -411,7 +452,8 @@ class SockoptStreamSettings extends CommonClass {
tcpFastOpen: this.tcpFastOpen,
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
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 host = url.searchParams.get('host') ?? undefined;
let path = url.searchParams.get('path') ?? undefined;
let mode = url.searchParams.get('mode') ?? undefined;
if (type === 'tcp' || type === 'none') {
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);

View File

@@ -31,6 +31,8 @@ class AllSetting {
this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = "";
this.externalTrafficInformEnable = false;
this.externalTrafficInformURI = "";
this.subCertFile = "";
this.subKeyFile = "";
this.subUpdates = 12;
@@ -43,7 +45,7 @@ class AllSetting {
this.subJsonMux = "";
this.subJsonRules = "";
this.timeLocation = "Asia/Tehran";
this.timeLocation = "Local";
if (data == null) {
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
*/
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
*/
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 {
static randomIntRange(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
static randomInt(n) {
return this.randomIntRange(0, n);
}
static randomSeq(count) {
let str = '';
for (let i = 0; i < count; ++i) {
str += seq[this.randomInt(62)];
static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
let seq = '';
switch (type) {
case "hex":
seq += "0123456789abcdef";
break;
default:
if (hasNumbers) seq += "0123456789";
if (hasLowercase) seq += "abcdefghijklmnopqrstuvwxyz";
if (hasUppercase) seq += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
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() {
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
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]];
}
const lengths = [2, 4, 6, 8, 10, 12, 14, 16].sort(() => Math.random() - 0.5);
let shortIds = [];
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(',');
return lengths.map(len => this.randomSeq(len, { type: "hex" })).join(',');
}
static randomLowerAndNum(len) {
let str = '';
for (let i = 0; i < len; ++i) {
str += seq[this.randomInt(36)];
}
return str;
return this.randomSeq(len, { hasUppercase: false });
}
static randomUUID() {
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return template.replace(/[xy]/g, function (c) {
const randomValues = new Uint8Array(1);
crypto.getRandomValues(randomValues);
let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
if (window.location.protocol === "https:") {
return window.crypto.randomUUID();
} else {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
const randomValues = new Uint8Array(1);
window.crypto.getRandomValues(randomValues);
let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
}
}
static randomShadowsocksPassword() {
let array = new Uint8Array(32);
const array = new Uint8Array(32);
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)
};
}
}
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/session"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
@@ -49,8 +50,8 @@ func (a *IndexController) index(c *gin.Context) {
func (a *IndexController) login(c *gin.Context) {
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"))
return
}
@@ -68,29 +69,31 @@ func (a *IndexController) login(c *gin.Context) {
safeUser := template.HTMLEscapeString(form.Username)
safePass := template.HTMLEscapeString(form.Password)
safeSecret := template.HTMLEscapeString(form.LoginSecret)
if user == nil {
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)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
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()
if err != nil {
logger.Warning("Unable to get session's max age from DB")
}
err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil {
logger.Warning("Unable to set session's max age")
session.SetMaxAge(c, sessionMaxAge*60)
session.SetLoginUser(c, user)
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", user.Username)
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
logger.Infof("%s logged in successfully", safeUser)
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil)
}
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)
}
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"))
}

View File

@@ -16,47 +16,49 @@ type Msg struct {
}
type AllSetting struct {
WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
PageSize int `json:"pageSize" form:"pageSize"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"`
SubDomain string `json:"subDomain" form:"subDomain"`
SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubUpdates int `json:"subUpdates" form:"subUpdates"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
Datepicker string `json:"datepicker" form:"datepicker"`
WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
PageSize int `json:"pageSize" form:"pageSize"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
SubEnable bool `json:"subEnable" form:"subEnable"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
SubPath string `json:"subPath" form:"subPath"`
SubDomain string `json:"subDomain" form:"subDomain"`
SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubUpdates int `json:"subUpdates" form:"subUpdates"`
ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"`
ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
Datepicker string `json:"datepicker" form:"datepicker"`
}
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/qs/qs.min.js"></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/utils.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/langs.js"></script>
<script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
<script>
const basePath = '{{ .base_path }}';
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>
<tr-qr-bg class="qr-bg-sub">
<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>
</tr-qr-box>
@@ -18,7 +18,7 @@
<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-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>
</tr-qr-box>
@@ -27,7 +27,7 @@
<tr-qr-box class="qr-box">
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
<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-box>
</template>
@@ -41,7 +41,6 @@
dbInbound: new DBInbound(),
client: null,
qrcodes: [],
clipboard: null,
visible: false,
subId: '',
show: function(title = '', dbInbound, client) {
@@ -79,14 +78,12 @@
qrModal: qrModal,
},
methods: {
copyToClipboard(elementId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.qrModal.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.qrModal.clipboard.destroy();
});
copy(content) {
ClipboardManager
.copyText(content)
.then(() => {
app.$message.success('{{ i18n "copied" }}')
})
},
setQrCode(elementId, content) {
new QRious({

View File

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

View File

@@ -422,16 +422,16 @@
</a-input>
</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" }}'
@keydown.enter.native="login">
</password-input>
</a-password-input>
</a-form-item>
<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" }}'
@keydown.enter.native="login">
</password-input>
</a-password-input>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
@@ -449,9 +449,9 @@
<a-row justify="center" class="centered">
<a-col :span="24">
<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">
<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>
&nbsp;&nbsp;<span v-text="l.name"></span>
</a-select-option>
@@ -461,7 +461,7 @@
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<theme-switch-login></theme-switch-login>
<a-theme-switch-login></a-theme-switch-login>
</a-row>
</a-form-item>
</a-form>
@@ -493,7 +493,7 @@
lang: ""
},
async created() {
this.lang = getLang();
this.lang = LanguageManager.getLanguage();
this.secretEnable = await this.getSecretStatus();
},
methods: {

View File

@@ -14,10 +14,10 @@
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
<a-input v-model.trim="clientsBulkModal.emailPrefix"></a-input>
@@ -26,7 +26,7 @@
<a-input v-model.trim="clientsBulkModal.emailPostfix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "security" }}' v-if="inbound.protocol === Protocols.VMESS">
<a-select v-model="clientsBulkModal.security" :dropdown-class-name="themeSwitcher.currentTheme">
@@ -61,7 +61,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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 v-if="app.ipLimitEnable">
<template slot="label">
@@ -73,7 +73,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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>
<template slot="label">
@@ -85,7 +85,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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 label='{{ i18n "pages.client.delayedStart" }}'>
<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' }"
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
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">
</persian-datepicker>
</a-persian-datepicker>
</a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label">

View File

@@ -34,7 +34,7 @@
{{define "commonSider"}}
<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">
{{template "menuItems" .}}
</a-menu>
@@ -43,7 +43,7 @@
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</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">
{{template "menuItems" .}}
</a-menu>

View File

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

View File

@@ -2,10 +2,10 @@
<template>
<div>
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder">
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder">
<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>
</a-input>
</div>
@@ -13,15 +13,27 @@
{{end}}
{{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/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
<script>
const persianDatepicker = {};
Vue.component('persian-datepicker', {
props: ['placeholder', 'format', 'value'],
Vue.component('a-persian-datepicker', {
props: {
'format': {
type: undefined,
required: false,
},
'value': {
type: String,
required: false,
},
'placeholder': {
type: String,
required: false,
},
},
template: `{{template "component/persianDatepickerTemplate"}}`,
data() {
return {
@@ -57,4 +69,4 @@
}
});
</script>
{{end}}
{{end}}

View File

@@ -1,26 +1,18 @@
{{define "component/settingListItem"}}
<a-list-item style="padding: 20px">
<a-row v-if="type === 'textarea'">
<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-list-item :style="{ padding: padding }">
<a-row>
<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 :lg="24" :xl="12">
<template v-if="type === 'text'">
<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>
<slot name="control"></slot>
</a-col>
</a-row>
</a-list-item>
@@ -28,9 +20,38 @@
{{define "component/setting"}}
<script>
Vue.component('setting-list-item', {
props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"],
template: `{{template "component/settingListItem"}}`,
});
Vue.component('a-setting-list-item', {
props: {
'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>
{{end}}

View File

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

View File

@@ -6,9 +6,13 @@
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<span>Theme</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 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-sub-menu>
</a-menu>
@@ -17,12 +21,15 @@
{{define "component/themeSwitchTemplateLogin"}}
<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-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">
<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>
</a-menu-item>
</a-menu>
@@ -83,8 +90,7 @@
};
}
const themeSwitcher = createThemeSwitcher();
Vue.component('theme-switch', {
props: [],
Vue.component('a-theme-switch', {
template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({
themeSwitcher
@@ -96,8 +102,7 @@
document.getElementById('message').className = themeSwitcher.currentTheme;
}
});
Vue.component('theme-switch-login', {
props: [],
Vue.component('a-theme-switch-login', {
template: `{{template "component/themeSwitchTemplateLogin"}}`,
data: () => ({
themeSwitcher
@@ -110,4 +115,4 @@
}
});
</script>
{{end}}
{{end}}

View File

@@ -7,7 +7,7 @@
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
<a-input-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>
</a-modal>

View File

@@ -66,7 +66,10 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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 v-if="app.ipLimitEnable">
<template slot="label">
@@ -78,7 +81,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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 v-if="app.ipLimitEnable && client.limitIp > 0 && client.email && isEdit">
<template slot="label">
@@ -120,13 +123,13 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</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 v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
<a-tag :color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
[[ SizeFormatter.sizeFormat(clientStats.up) ]] /
[[ SizeFormatter.sizeFormat(clientStats.down) ]]
([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
@@ -151,8 +154,8 @@
</template>
<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>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item>
<a-form-item v-if="client.expiryTime != 0">

View File

@@ -41,7 +41,7 @@
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number>
</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' }"
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
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">
</persian-datepicker>
</a-persian-datepicker>
</a-form-item>
</a-form>

View File

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

View File

@@ -18,7 +18,7 @@
</table>
</a-collapse-panel>
</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-item label="Fallbacks">
<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-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>

View File

@@ -18,7 +18,7 @@
</table>
</a-collapse-panel>
</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-item label="Fallbacks">
<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-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:5px 0;"></a-divider>

View File

@@ -7,7 +7,7 @@
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
@@ -43,11 +43,11 @@
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</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-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 label=" ">
<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-form-item>
<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 :wrapper-col="{span:24}">
<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="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select>
</a-form-item>
<a-form-item>

View File

@@ -6,22 +6,22 @@
</a-form-item>
<template v-if="inbound.stream.sockoptSwitch">
<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 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 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 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 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 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 label="Proxy Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
@@ -32,8 +32,8 @@
<a-form-item label="Multipath TCP">
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item>
<a-form-item label="TCP No-Delay" v-if="inbound.stream.sockopt.tcpMptcp">
<a-switch v-model.trim="inbound.stream.sockopt.tcpNoDelay"></a-switch>
<a-form-item label="Penetrate">
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
</a-form-item>
<a-form-item label="V6 Only">
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>

View File

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

View File

@@ -34,7 +34,7 @@
</a-input-group>
</a-form-item>
<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">
<a-select-option value=''>None</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-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
</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">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
@@ -82,10 +85,10 @@
</template>
<template v-else>
<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 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>
</template>
<a-form-item label='OCSP stapling'>

View File

@@ -55,18 +55,18 @@
<template slot="content" v-if="client.email">
<table cellpadding="2" width="100%">
<tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
<td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr>
<tr v-if="client.totalGB > 0">
<td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
<td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr>
</table>
</template>
<table>
<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">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td>
@@ -124,9 +124,9 @@
</template>
</span>
</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-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">
<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>
@@ -172,7 +172,7 @@
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
</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">
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td>
@@ -181,12 +181,12 @@
<template slot="content" v-if="client.email">
<table cellpadding="2" width="100%">
<tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
<td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr>
<tr>
<td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
<td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr>
</table>
</template>
@@ -244,7 +244,7 @@
</template>
</span>
</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-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">

View File

@@ -181,8 +181,35 @@
<tr v-if="infoModal.clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
<a-tag color="green">[[ SizeFormatter.sizeFormat(infoModal.clientStats.up + 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>
</tr>
</table>
@@ -197,7 +224,7 @@
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
</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">
<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>
@@ -206,7 +233,7 @@
</td>
<td>
<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'">
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</template>
@@ -231,7 +258,7 @@
<tr-info-title class="tr-info-title">
<a-tag color="purple">Subscription Link</a-tag>
<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>
</tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
@@ -240,7 +267,7 @@
<tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag>
<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>
</tr-info-title>
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
@@ -252,7 +279,7 @@
<tr-info-title class="tr-info-title">
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<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>
</tr-info-title>
</tr-info-row>
@@ -263,7 +290,7 @@
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<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>
</tr-info-title>
<code>[[ link.link ]]</code>
@@ -277,7 +304,7 @@
<tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<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>
</tr-info-title>
<code>[[ link.link ]]</code>
@@ -400,11 +427,11 @@
</tr>
<tr>
<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">
<a-tag color="blue">Config</a-tag>
<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>
</tr-info-title>
<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>
</a-modal>
<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 = {
visible: false,
inbound: new Inbound(),
@@ -425,12 +464,12 @@
clientStats: [],
upStats: 0,
downStats: 0,
clipboard: null,
links: [],
index: null,
isExpired: false,
subLink: '',
subJsonLink: '',
clientIps: '',
show(dbInbound, index) {
this.index = index;
this.inbound = dbInbound.toInbound();
@@ -438,6 +477,12 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
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) : [];
if (app.ipLimitEnable && this.clientSettings.limitIp) {
refreshIPs(this.clientStats.email).then((ips) => {
this.clientIps = ips;
})
}
if (this.inbound.protocol == Protocols.WIREGUARD) {
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else {
@@ -466,6 +511,7 @@
el: '#inbound-info-modal',
data: {
infoModal,
refreshing: false,
get dbInbound() {
return this.infoModal.dbInbound;
},
@@ -486,21 +532,39 @@
},
},
methods: {
copyToClipboard(elementId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.infoModal.clipboard.destroy();
});
copy(content) {
ClipboardManager
.copyText(content)
.then(() => {
app.$message.success('{{ i18n "copied" }}')
})
},
statsColor(stats) {
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
},
getRemStats() {
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-col :xs="24" :sm="24" :lg="12">
{{ 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 :xs="24" :sm="24" :lg="12">
{{ 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 :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.inboundCount" }}:
@@ -375,19 +375,19 @@
<template slot="content">
<table cellpadding="2" width="100%">
<tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]]
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template>
<template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -408,7 +408,7 @@
<template v-else slot="content">
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
</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) ]]
</a-tag>
</a-popover>
@@ -474,19 +474,19 @@
<template slot="content">
<table cellpadding="2" width="100%">
<tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr>
</table>
</template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]]
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template>
<template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -544,9 +544,7 @@
</a-layout>
</a-layout>
{{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/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
@@ -885,7 +883,7 @@
this.exportSubs(dbInbound.id);
break;
case "clipboard":
this.copyToClipboard(dbInbound.id);
this.copy(dbInbound.id);
break;
case "resetTraffic":
this.resetTraffic(dbInbound.id);
@@ -929,7 +927,7 @@
expiryTime: dbInbound.expiryTime,
listen: '',
port: RandomUtil.randomIntRange(10000, 60000),
port: RandomUtil.randomInteger(10000, 60000),
protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(),
@@ -1108,6 +1106,36 @@
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) {
switch (protocol) {
case Protocols.TROJAN: return client.password;
@@ -1246,9 +1274,9 @@
return remained>0 ? remained : 0;
},
clientStatsColor(dbInbound, email) {
if (email.length == 0) return clientUsageColor();
if (email.length == 0) return ColorUtils.clientUsageColor();
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
return clientUsageColor(clientStats, app.trafficDiff)
return ColorUtils.clientUsageColor(clientStats, app.trafficDiff)
},
statsProgress(dbInbound, email) {
if (email.length == 0) return 100;
@@ -1266,17 +1294,17 @@
},
remainedDays(expTime){
if (expTime == 0) return null;
if (expTime < 0) return formatSecond(expTime/-1000);
if (expTime < 0) return TimeFormatter.formatSecond(expTime/-1000);
now = new Date().getTime();
if (expTime < now) return '{{ i18n "depleted" }}';
return formatSecond((expTime-now)/1000);
return TimeFormatter.formatSecond((expTime-now)/1000);
},
statsExpColor(dbInbound, email){
if (email.length == 0) return '#7a316f';
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
if (!clientStats) return '#7a316f';
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
statsColor = ColorUtils.usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
expColor = ColorUtils.usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
switch (true) {
case statsColor == "red" || expColor == "red":
return "#cf3c3c"; // Red
@@ -1310,7 +1338,7 @@
if (clients != null){
clients.forEach(c => {
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){
clients.forEach(c => {
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');
},
copyToClipboard(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
copy(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
},
async startDataRefreshLoop() {
while (this.isRefreshEnabled) {
@@ -1410,7 +1438,7 @@
}
},
watch: {
searchKey: debounce(function (newVal) {
searchKey: Utils.debounce(function (newVal) {
this.searchInbounds(newVal);
}, 500)
},

View File

@@ -19,6 +19,18 @@
.ant-card-dark h2 {
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>
<body>
@@ -45,11 +57,11 @@
<a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color"
: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>
<template slot="title">
<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>
</a-tooltip></div>
</a-col>
@@ -58,7 +70,7 @@
:stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress>
<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>
</a-col>
</a-row>
@@ -70,7 +82,7 @@
:stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress>
<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>
</a-col>
<a-col :span="12" style="text-align: center">
@@ -78,7 +90,7 @@
:stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress>
<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>
</a-col>
</a-row>
@@ -99,8 +111,8 @@
<a-col :sm="24" :lg="12">
<a-card hoverable>
<b>{{ i18n "pages.index.operationHours" }}:</b>
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :lg="12">
@@ -145,7 +157,7 @@
<a-col :sm="24" :lg="12">
<a-card hoverable>
<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-card>
</a-col>
@@ -207,7 +219,7 @@
<a-col :span="12">
<a-tag>
<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">
{{ i18n "pages.index.upSpeed" }}
</template>
@@ -217,7 +229,7 @@
<a-col :span="12">
<a-tag>
<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">
{{ i18n "pages.index.downSpeed" }}
</template>
@@ -236,7 +248,7 @@
<a-icon type="cloud-upload"></a-icon>
<template slot="title">
{{ i18n "pages.index.totalSent" }}
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
</template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
</a-tooltip>
</a-tag>
</a-col>
@@ -246,7 +258,7 @@
<a-icon type="cloud-download"></a-icon>
<template slot="title">
{{ i18n "pages.index.totalReceive" }}
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
</template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
</a-tooltip>
</a-tag>
</a-col>
@@ -260,14 +272,16 @@
</a-layout>
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
@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>
<template v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px"
@click="switchV2rayVersion(version)">
[[ version ]]
</a-tag>
</template>
<a-list class="ant-xray-version-list" bordered style="width: 100%;">
<a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
<a-list-item-meta>
<template #title>[[ version ]]</template>
</a-list-item-meta>
<a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
</a-list-item>
</a-list>
</a-modal>
<a-modal id="log-modal" v-model="logModal.visible"
:closable="true" @cancel="() => logModal.visible = false"
@@ -314,25 +328,34 @@
</a-form>
<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 id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
:closable="true" footer=""
<a-modal id="backup-modal"
v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
:closable="true"
footer=""
:class="themeSwitcher.currentTheme">
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
:message="backupModal.description"
show-icon>
</a-alert>
<a-space direction="horizontal" style="text-align: center; margin-bottom: 10px;">
<a-button type="primary" @click="exportDatabase()">
[[ backupModal.exportText ]]
</a-button>
<a-button type="primary" @click="importDatabase()">
[[ backupModal.importText ]]
</a-button>
</a-space>
<a-list class="ant-backup-list" bordered style="width: 100%;">
<a-list-item class="ant-backup-list-item" @click="exportDatabase()">
<a-list-item-meta>
<template #title>{{ i18n "pages.index.exportDatabase" }}</template>
<template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
</a-list-item-meta>
<a-icon type="right" />
</a-list-item>
<a-list-item class="ant-backup-list-item" @click="importDatabase()">
<a-list-item-meta>
<template #title>{{ i18n "pages.index.importDatabase" }}</template>
<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-layout>
{{template "js" .}}
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}}
{{template "textModal"}}
<script>
@@ -354,7 +377,7 @@
if (this.total === 0) {
return 0;
}
return toFixed(this.current / this.total * 100, 2);
return NumberFormatter.toFixed(this.current / this.total * 100, 2);
}
get color() {
@@ -397,7 +420,7 @@
this.logicalPro = data.logicalPro;
this.cpuSpeedMhz = data.cpuSpeedMhz;
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.netIO = data.netIO;
this.netTraffic = data.netTraffic;
@@ -492,24 +515,11 @@
const backupModal = {
visible: false,
title: '',
description: '',
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;
show() {
this.visible = true;
},
hide() {
this.visible = false;
this.visible = false;
},
};
@@ -605,12 +615,7 @@
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
},
openBackup() {
backupModal.show({
title: '{{ i18n "pages.index.backupTitle" }}',
description: '{{ i18n "pages.index.backupDescription" }}',
exportText: '{{ i18n "pages.index.exportDatabase" }}',
importText: '{{ i18n "pages.index.importDatabase" }}',
});
backupModal.show();
},
exportDatabase() {
window.location = basePath + 'server/getDb';

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
</a-form-item>
<a-form-item label='Protocol'>
<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-form-item>
<a-form-item label='Attributes'>

View File

@@ -106,7 +106,7 @@ func (j *CheckClientIpJob) hasLimitIp() 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: (.+)$`)
accessLogPath, _ := xray.GetAccessLogPath()
@@ -151,13 +151,13 @@ func (j *CheckClientIpJob) processLogFile() bool {
}
sort.Strings(ips)
inboundClientIps, err := j.getInboundClientIps(email)
clientIpsRecord, err := j.getInboundClientIps(email)
if err != nil {
j.addInboundClientIps(email, ips)
continue
}
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog
shouldCleanLog = j.updateInboundClientIps(clientIpsRecord, email, ips) || shouldCleanLog
}
return shouldCleanLog
@@ -309,12 +309,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
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 {
return nil, err
}
return inbounds, nil
return inbound, nil
}

View File

@@ -23,7 +23,7 @@ func (j *CheckCpuJob) Run() {
threshold, _ := j.settingService.GetTgCpu()
// 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) {
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),

View File

@@ -1,11 +1,16 @@
package job
import (
"encoding/json"
"x-ui/logger"
"x-ui/web/service"
"x-ui/xray"
"github.com/valyala/fasthttp"
)
type XrayTrafficJob struct {
settingService service.SettingService
xrayService service.XrayService
inboundService service.InboundService
outboundService service.OutboundService
@@ -31,7 +36,36 @@ func (j *XrayTrafficJob) Run() {
if err != nil {
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 {
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": {
"statsInboundDownlink": true,
"statsInboundUplink": true,
"statsOutboundDownlink": true,
"statsOutboundUplink": true
"statsOutboundDownlink": false,
"statsOutboundUplink": false
}
},
"routing": {

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,7 @@
"monitor" = "Listen IP"
"certificate" = "Digital Certificate"
"fail" = "Failed"
"comment" = "Comment"
"success" = "Successfully"
"getVersion" = "Get Version"
"install" = "Install"
@@ -60,6 +61,10 @@
"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."
"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]
"dashboard" = "Overview"
@@ -108,9 +113,10 @@
"config" = "Config"
"backup" = "Backup & Restore"
"backupTitle" = "Database Backup & Restore"
"backupDescription" = "It is recommended to make a backup before restoring a database."
"exportDatabase" = "Back Up"
"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device."
"importDatabase" = "Restore"
"importDatabaseDesc" = "Click to select and upload a .db file from your device to restore your database from a backup."
[pages.inbounds]
"title" = "Inbounds"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI"
"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"
"fragmentDesc" = "Enable fragmentation for TLS hello packet."
"fragmentSett" = "Fragmentation Settings"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Settings"
"direct" = "Direct Connection"
"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]
"title" = "Xray Configs"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Whether to enable DNS query logs"
"maskAddress" = "Mask Address"
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
"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]
"first" = "First"
@@ -426,6 +453,14 @@
"enableDesc" = "Enable built-in DNS server"
"tag" = "DNS Inbound Tag"
"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"
"strategyDesc" = "Overall strategy to resolve domain names"
"add" = "Add Server"
@@ -440,7 +475,7 @@
"poolSize" = "Pool Size"
[pages.settings.security]
"admin" = "Admin"
"admin" = "Admin credentials"
"secret" = "Secret Token"
"loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
@@ -483,9 +518,9 @@
"status" = "✅ Bot is OK!"
"usage" = "❗ Please provide a text to search!"
"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>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operation successful!"
"restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core is not running."

View File

@@ -44,6 +44,7 @@
"monitor" = "Listening IP"
"certificate" = "Certificado Digital"
"fail" = "Falló"
"comment" = "Comentario"
"success" = "Éxito"
"getVersion" = "Obtener versión"
"install" = "Instalar"
@@ -60,6 +61,10 @@
"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."
"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]
"dashboard" = "Estado del Sistema"
@@ -108,9 +113,10 @@
"config" = "Configuración"
"backup" = "Copia de Seguridad y Restauración"
"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" = "Descargar Base de Datos"
"importDatabase" = "Cargar Base de Datos"
"exportDatabase" = "Copia de seguridad"
"exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo."
"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]
"title" = "Entradas"
@@ -305,6 +311,10 @@
"subShowInfo" = "Mostrar información de uso"
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
"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"
"fragment" = "Fragmentación"
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"
@@ -316,7 +326,15 @@
"muxSett" = "Configuración Mux"
"direct" = "Conexión Directa"
"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]
"title" = "Xray Configuración"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Si habilitar los registros de consulta DNS"
"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."
"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]
"first" = "Primero"
@@ -426,6 +453,14 @@
"enableDesc" = "Habilitar servidor DNS incorporado"
"tag" = "Etiqueta de Entrada DNS"
"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"
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
"add" = "Agregar Servidor"
@@ -440,7 +475,7 @@
"poolSize" = "Tamaño del grupo"
[pages.settings.security]
"admin" = "Administrador"
"admin" = "Credenciales de administrador"
"secret" = "Token Secreto"
"loginSecurity" = "Seguridad de Inicio de Sesión"
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
@@ -483,9 +518,9 @@
"status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"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>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ ¡Operación exitosa!"
"restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core no está en ejecución."

View File

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

View File

@@ -44,6 +44,7 @@
"monitor" = "IP Pemantauan"
"certificate" = "Sertifikat Digital"
"fail" = "Gagal"
"comment" = "Komentar"
"success" = "Berhasil"
"getVersion" = "Dapatkan Versi"
"install" = "Instal"
@@ -60,6 +61,10 @@
"secAlertPanelURI" = "Jalur URI default panel 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."
"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]
"dashboard" = "Ikhtisar"
@@ -108,9 +113,10 @@
"config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database"
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
"exportDatabase" = "Cadangkan"
"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda."
"importDatabase" = "Pulihkan"
"importDatabaseDesc" = "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan."
[pages.inbounds]
"title" = "Masuk"
@@ -305,7 +311,10 @@
"subShowInfo" = "Tampilkan Info Penggunaan"
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
"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"
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
"fragmentSett" = "Pengaturan Fragmentasi"
@@ -316,7 +325,15 @@
"muxSett" = "Pengaturan Mux"
"direct" = "Koneksi langsung"
"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]
"title" = "Konfigurasi Xray"
@@ -369,6 +386,15 @@
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
"maskAddress" = "Alamat Masker"
"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]
"first" = "Pertama"
@@ -426,6 +452,14 @@
"enableDesc" = "Aktifkan server DNS bawaan"
"tag" = "Tanda DNS Masuk"
"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"
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
"add" = "Tambahkan Server"
@@ -440,7 +474,7 @@
"poolSize" = "Ukuran Kolam"
[pages.settings.security]
"admin" = "Admin"
"admin" = "Kredensial admin"
"secret" = "Token Rahasia"
"loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
@@ -483,9 +517,9 @@
"status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!"
"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>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operasi berhasil!"
"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core tidak berjalan."

View File

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

View File

@@ -44,6 +44,7 @@
"monitor" = "IP de Escuta"
"certificate" = "Certificado Digital"
"fail" = "Falhou"
"comment" = "Comentário"
"success" = "Com Sucesso"
"getVersion" = "Obter Versão"
"install" = "Instalar"
@@ -60,6 +61,10 @@
"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."
"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]
"dashboard" = "Visão Geral"
@@ -108,9 +113,10 @@
"config" = "Configuração"
"backup" = "Backup e Restauração"
"backupTitle" = "Backup e Restauração do Banco de Dados"
"backupDescription" = "É recomendado fazer um backup antes de restaurar o banco de dados."
"exportDatabase" = "Fazer Backup"
"exportDatabase" = "Backup"
"exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo."
"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]
"title" = "Inbounds"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente."
"subURI" = "URI de Proxy Reverso"
"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"
"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello."
"fragmentSett" = "Configurações de Fragmentação"
@@ -316,7 +326,15 @@
"muxSett" = "Configurações de Mux"
"direct" = "Conexão Direta"
"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]
"title" = "Configurações Xray"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Se ativar logs de consulta DNS"
"maskAddress" = "Mascarar Endereço"
"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]
"first" = "Primeiro"
@@ -426,6 +453,14 @@
"enableDesc" = "Ativar o servidor DNS integrado"
"tag" = "Tag de Entrada DNS"
"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"
"strategyDesc" = "Estratégia geral para resolver nomes de domínio"
"add" = "Adicionar Servidor"
@@ -440,7 +475,7 @@
"poolSize" = "Tamanho do Pool"
[pages.settings.security]
"admin" = "Admin"
"admin" = "Credenciais de administrador"
"secret" = "Token Secreto"
"loginSecurity" = "Login Seguro"
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
@@ -483,9 +518,9 @@
"status" = "✅ Bot está OK!"
"usage" = "❗ Por favor, forneça um texto para pesquisar!"
"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>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ Operação bem-sucedida!"
"restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core não está em execução."

View File

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

View File

@@ -44,6 +44,7 @@
"monitor" = "Dinleme IP"
"certificate" = "Dijital Sertifika"
"fail" = "Başarısız"
"comment" = "Yorum"
"success" = "Başarılı"
"getVersion" = "Sürümü Al"
"install" = "Yükle"
@@ -60,6 +61,10 @@
"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."
"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]
"dashboard" = "Genel Bakış"
@@ -108,9 +113,10 @@
"config" = "Yapılandırma"
"backup" = "Yedekle & Geri Yükle"
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"
"backupDescription" = "Veritabanını geri yüklemeden önce yedek almanız önerilir."
"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"
"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]
"title" = "Gelenler"
@@ -306,6 +312,10 @@
"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir."
"subURI" = "Ters Proxy URI"
"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"
"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir."
"fragmentSett" = "Parçalama Ayarları"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Ayarları"
"direct" = "Doğrudan Bağlantı"
"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]
"title" = "Xray Yapılandırmaları"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
"maskAddress" = "Adres Maskesi"
"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]
"first" = "İlk"
@@ -426,6 +453,14 @@
"enableDesc" = "Dahili DNS sunucusunu etkinleştir"
"tag" = "DNS Gelen Etiketi"
"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"
"strategyDesc" = "Alan adlarını çözmek için genel strateji"
"add" = "Sunucu Ekle"
@@ -440,7 +475,7 @@
"poolSize" = "Havuz Boyutu"
[pages.settings.security]
"admin" = "Yönetici"
"admin" = "Yönetici kimlik bilgileri"
"secret" = "Gizli Anahtar"
"loginSecurity" = "Güvenli Giriş"
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
@@ -483,9 +518,9 @@
"status" = "✅ Bot çalışıyor!"
"usage" = "❗ Lütfen aramak için bir metin sağlayın!"
"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>"
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
"restartUsage" = "\r\n\r\n<code>/restart</code>"
"restartSuccess" = "✅ İşlem başarılı!"
"restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core çalışmıyor."

View File

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

View File

@@ -44,6 +44,7 @@
"monitor" = "Listening IP"
"certificate" = "Chứng chỉ số"
"fail" = "Thất bại"
"comment" = "Bình luận"
"success" = "Thành công"
"getVersion" = "Lấy phiên bản"
"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."
"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."
"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]
"dashboard" = "Trạng thái hệ thống"
@@ -108,9 +113,10 @@
"config" = "Cấu hình"
"backup" = "Sao lưu & Khôi phục"
"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" = "Tải về Cơ sở dữ liệu"
"importDatabase" = "Tải lên Cơ sở dữ liệu"
"exportDatabase" = "Sao lư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" = "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]
"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"
"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"
"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"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
"fragmentSett" = "Cài đặt phân mảnh"
@@ -316,7 +326,15 @@
"muxSett" = "Mux Cài đặt"
"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ể."
"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]
"title" = "Cài đặt Xray"
@@ -369,6 +387,15 @@
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không"
"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ý."
"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]
"first" = "Đầu tiên"
@@ -426,6 +453,14 @@
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
"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."
"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"
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
"add" = "Thêm máy chủ"
@@ -440,7 +475,7 @@
"poolSize" = "Kích thước bể bơi"
[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"
"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"
@@ -483,9 +518,9 @@
"status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"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>"
"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!"
"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."

View File

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

View File

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

362
x-ui.sh
View File

@@ -2,6 +2,7 @@
red='\033[0;31m'
green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m'
plain='\033[0m'
@@ -86,6 +87,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
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
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:"
@@ -103,6 +108,7 @@ else
echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1
fi
@@ -179,7 +185,7 @@ update_menu() {
return 0
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/bin/x-ui
@@ -279,7 +285,7 @@ reset_webbasepath() {
# Apply the new web base path setting
/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 "${green}Please use the new web base path to access the panel.${plain}"
restart
@@ -439,7 +445,7 @@ show_log() {
1)
journalctl -u x-ui -e --no-pager -f -p debug
if [[ $# == 0 ]]; then
before_show_menu
before_show_menu
fi
;;
2)
@@ -457,9 +463,9 @@ show_log() {
show_banlog() {
local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
@@ -503,7 +509,7 @@ bbr_menu() {
disable_bbr
bbr_menu
;;
*)
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
bbr_menu
;;
@@ -546,7 +552,7 @@ enable_bbr() {
centos | almalinux | rocky | ol)
yum -y update && yum -y install ca-certificates
;;
fedora | amzn)
fedora | amzn | virtuozzo)
dnf -y update && dnf -y install ca-certificates
;;
arch | manjaro | parch)
@@ -574,14 +580,14 @@ enable_bbr() {
}
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
echo ""
LOGE "Failed to download script, Please check whether the machine can connect Github"
before_show_menu
else
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
fi
}
@@ -682,10 +688,13 @@ show_xray_status() {
}
firewall_menu() {
echo -e "${green}\t1.${plain} Install Firewall & open ports"
echo -e "${green}\t2.${plain} Allowed List"
echo -e "${green}\t3.${plain} Delete Ports from List"
echo -e "${green}\t4.${plain} Disable Firewall"
echo -e "${green}\t1.${plain} ${green}Install${plain} Firewall"
echo -e "${green}\t2.${plain} Port List [numbered]"
echo -e "${green}\t3.${plain} ${green}Open${plain} Ports"
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"
read -p "Choose an option: " choice
case "$choice" in
@@ -693,29 +702,41 @@ firewall_menu() {
show_menu
;;
1)
open_ports
install_firewall
firewall_menu
;;
2)
sudo ufw status
ufw status numbered
firewall_menu
;;
3)
delete_ports
open_ports
firewall_menu
;;
4)
sudo ufw disable
delete_ports
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"
firewall_menu
;;
esac
}
open_ports() {
install_firewall() {
if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..."
apt-get update
@@ -733,13 +754,16 @@ open_ports() {
ufw allow ssh
ufw allow http
ufw allow https
ufw allow 2053/tcp
ufw allow 2053/tcp #webPort
ufw allow 2096/tcp #subport
# Enable the firewall
ufw --force enable
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
# Check if the input is valid
@@ -755,66 +779,109 @@ open_ports() {
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Open the port range
ufw allow $start_port:$end_port/tcp
ufw allow $start_port:$end_port/udp
else
# Open the single port
ufw allow "$port"
fi
done
# Confirm that the ports are open
echo "The following ports are now open:"
ufw status | grep "ALLOW" | grep -Eo "[0-9]+(/[a-z]+)?"
echo "Firewall status:"
ufw status verbose
# Confirm that the ports are opened
echo "Opened 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 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() {
# Prompt the user to enter the ports they want to delete
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
# Display current rules with numbers
echo "Current UFW rules:"
ufw status numbered
# Check if the input is valid
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
# Ask the user how they want to delete rules
echo "Do you want to delete rules by:"
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
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() {
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}\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"
read -p "Choose an option: " choice
@@ -842,10 +909,10 @@ update_geo() {
;;
3)
systemctl stop x-ui
rm -f geoip_VN.dat geosite_VN.dat
wget -O geoip_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat -N https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
echo -e "${green}vuong2023 datasets have been updated successfully!${plain}"
rm -f geoip_RU.dat geosite_RU.dat
wget -O geoip_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -O geosite_RU.dat -N https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
echo -e "${green}runetfreedom datasets have been updated successfully!${plain}"
restart
;;
*)
@@ -1006,7 +1073,7 @@ ssl_cert_issue() {
centos | almalinux | rocky | ol)
yum -y update && yum -y install socat
;;
fedora | amzn)
fedora | amzn | virtuozzo)
dnf -y update && dnf -y install socat
;;
arch | manjaro | parch)
@@ -1278,8 +1345,8 @@ run_speedtest() {
}
create_iplimit_jails() {
# Use default bantime if not passed => 15 minutes
local bantime="${1:-15}"
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in 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
[INCLUDES]
before = iptables-common.conf
before = iptables-allports.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
@@ -1353,15 +1420,22 @@ iplimit_remove_conflicts() {
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() {
echo -e "\n${green}\t1.${plain} Install Fail2ban and configure IP Limit"
echo -e "${green}\t2.${plain} Change Ban Duration"
echo -e "${green}\t3.${plain} Unban Everyone"
echo -e "${green}\t4.${plain} Ban Logs"
echo -e "${green}\t5.${plain} Real-Time Logs"
echo -e "${green}\t6.${plain} Service Status"
echo -e "${green}\t7.${plain} Service Restart"
echo -e "${green}\t8.${plain} Uninstall Fail2ban and IP Limit"
echo -e "${green}\t5.${plain} Ban an IP Address"
echo -e "${green}\t6.${plain} Unban an IP Address"
echo -e "${green}\t7.${plain} Real-Time Logs"
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"
read -p "Choose an option: " choice
case "$choice" in
@@ -1403,22 +1477,44 @@ iplimit_main() {
iplimit_main
;;
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
;;
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
;;
7)
systemctl restart fail2ban
tail -f /var/log/fail2ban.log
iplimit_main
;;
8)
service fail2ban status
iplimit_main
;;
9)
systemctl restart fail2ban
iplimit_main
;;
10)
remove_iplimit
iplimit_main
;;
*)
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
iplimit_main
;;
@@ -1445,7 +1541,7 @@ install_iplimit() {
yum update -y && yum install epel-release -y
yum -y install fail2ban
;;
fedora | amzn)
fedora | amzn | virtuozzo)
dnf -y update && dnf -y install fail2ban
;;
arch | manjaro | parch)
@@ -1526,7 +1622,7 @@ remove_iplimit() {
yum remove fail2ban -y
yum autoremove -y
;;
fedora | amzn)
fedora | amzn | virtuozzo)
dnf remove fail2ban -y
dnf autoremove -y
;;
@@ -1629,61 +1725,63 @@ SSH_port_forwarding() {
}
show_usage() {
echo "x-ui control menu usages: "
echo "------------------------------------------"
echo -e "SUBCOMMANDS:"
echo -e "x-ui - Admin Management Script"
echo -e "x-ui start - Start"
echo -e "x-ui stop - Stop"
echo -e "x-ui restart - Restart"
echo -e "x-ui status - Current Status"
echo -e "x-ui settings - Current Settings"
echo -e "x-ui enable - Enable Autostart on OS Startup"
echo -e "x-ui disable - Disable Autostart on OS Startup"
echo -e "x-ui log - Check logs"
echo -e "x-ui banlog - Check Fail2ban ban logs"
echo -e "x-ui update - Update"
echo -e "x-ui custom - custom version"
echo -e "x-ui install - Install"
echo -e "x-ui uninstall - Uninstall"
echo "------------------------------------------"
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
│ │
${blue}x-ui${plain} - Admin Management Script
${blue}x-ui start${plain} - Start │
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status
${blue}x-ui settings${plain} - Current Settings
${blue}x-ui enable${plain} - Enable Autostart on OS Startup
${blue}x-ui disable${plain} - Disable Autostart on OS Startup
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
}
show_menu() {
echo -e "
${green}3X-UI Panel Management Script${plain}
${green}0.${plain} Exit Script
————————————————
${green}1.${plain} Install
${green}2.${plain} Update
${green}3.${plain} Update Menu
${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}8.${plain} Reset Settings
${green}9.${plain} Change Port
${green}10.${plain} View Current Settings
————————————————
${green}11.${plain} Start
${green}12.${plain} Stop
${green}13.${plain} Restart
${green}14.${plain} Check Status
${green}15.${plain} Logs Management
————————————————
${green}16.${plain} Enable Autostart
${green}17.${plain} Disable Autostart
————————————————
${green}18.${plain} SSL Certificate Management
${green}19.${plain} Cloudflare SSL Certificate
${green}20.${plain} IP Limit 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}25.${plain} Speedtest by Ookla
╔────────────────────────────────────────────────╗
${green}3X-UI Panel Management Script${plain}
${green}0.${plain} Exit Script │
│────────────────────────────────────────────────│
${green}1.${plain} Install │
${green}2.${plain} Update
${green}3.${plain} Update Menu │
${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}8.${plain} Reset Settings │
${green}9.${plain} Change Port │
${green}10.${plain} View Current Settings │
│────────────────────────────────────────────────│
${green}11.${plain} Start │
${green}12.${plain} Stop │
${green}13.${plain} Restart │
${green}14.${plain} Check Status │
${green}15.${plain} Logs Management │
│────────────────────────────────────────────────│
${green}16.${plain} Enable Autostart
${green}17.${plain} Disable Autostart │
│────────────────────────────────────────────────│
${green}18.${plain} SSL Certificate Management │
${green}19.${plain} Cloudflare SSL Certificate │
${green}20.${plain} IP Limit 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}25.${plain} Speedtest by Ookla │
╚────────────────────────────────────────────────╝
"
show_status
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
}
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
messages := strings.Split(message, "\n")
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
messages := strings.SplitSeq(message, "\n")
for _, msg := range messages {
for msg := range messages {
matches := regex.FindStringSubmatch(msg)
if len(matches) > 3 {
level := matches[2]
msgBody := matches[3]
// Map the level to the appropriate logger function
switch level {
case "Debug":
logger.Debug("XRAY: " + msgBody)
case "Info":
logger.Info("XRAY: " + msgBody)
case "Warning":
logger.Warning("XRAY: " + msgBody)
case "Error":
if strings.Contains(strings.ToLower(msgBody), "failed") {
logger.Error("XRAY: " + msgBody)
default:
logger.Debug("XRAY: " + msg)
} else {
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 = ""
} 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
}
}