Compare commits

..

162 Commits

Author SHA1 Message Date
MHSanaei
c500233a58 minor changes 2024-02-24 03:19:28 +03:30
MHSanaei
c7926d0bc0 [log] fix download format
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-24 03:02:05 +03:30
MHSanaei
034bc5e228 v2.2.0 2024-02-23 18:49:37 +03:30
MHSanaei
a39d07a68a revert #1678
i got so many errors while testing it on my server
and i think we can have security issue if use this
anyway thank you and sorry about this
2024-02-23 17:39:43 +03:30
MHSanaei
81c9b4450b minor changes
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
Co-Authored-By: Tara Rostami <132676256+TaraRostami@users.noreply.github.com>
2024-02-23 17:37:32 +03:30
Tara Rostami
fc3ea2dd4b Ultra Dark Theme for 3X-UI (#1871) 2024-02-22 22:50:38 +03:30
MHSanaei
fe7a5f1813 better view 2024-02-22 22:49:18 +03:30
MHSanaei
3cd1b59a6c [bug] fix wg reserved
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-22 22:40:25 +03:30
MHSanaei
7ec6989c99 [rule] clearable outbound & balancer
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-22 22:40:01 +03:30
Shahin
ee4d7a02a9 Update xray.html (#1864)
* Fix Dockerfile (#1854)

Fix wrong image

* Update xray.html

---------

Co-authored-by: LOVECHEN <lovechen@me.com>
2024-02-22 22:02:49 +03:30
dependabot[bot]
d6fd1c7ff0 Bump google.golang.org/grpc from 1.61.1 to 1.62.0 (#1873)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.1 to 1.62.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.61.1...v1.62.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 12:31:31 +03:30
MHSanaei
5fbd5e8518 release as a draft 2024-02-22 11:55:34 +03:30
MHSanaei
c31882cb92 bug fix #1595 2024-02-21 22:20:51 +03:30
MHSanaei
81d47f7512 [xray] add meta, apple, reddit option to warp 2024-02-21 17:48:23 +03:30
MHSanaei
0baa204ce9 Bash - BBR Disable Option 2024-02-21 16:16:45 +03:30
MHSanaei
660e5ad101 [dark] change message by theme
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 15:32:18 +03:30
MHSanaei
aebf52efb2 simplify log and text modals
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 15:16:27 +03:30
MHSanaei
c83a1db0c8 [ui] fix loading function
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 15:09:56 +03:30
MHSanaei
865d3e08e7 [wg] fix subnet in peer
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 15:07:45 +03:30
MHSanaei
91ee6dc7cb [wg] new peer with one IP
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 15:07:29 +03:30
MHSanaei
7708bb9af2 [xray] fakedns support
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 14:51:46 +03:30
MHSanaei
03b7a34793 [sub] json + fragment
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 14:17:52 +03:30
MHSanaei
f3eb4f055d SSL Security Alert
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-21 12:06:49 +03:30
somebodywashere
c61575ac9a Create directory for IPLimit files if needed (#1852) 2024-02-21 12:00:28 +03:30
LOVECHEN
f8796386dc [docker] use go 1.22 (#1854)
Fix wrong image
2024-02-21 11:43:09 +03:30
MHSanaei
ad38481312 Update release.yml 2024-02-20 16:16:03 +03:30
MHSanaei
937285ea3b Update go.mod to specify Go 1.22 2024-02-20 01:14:59 +03:30
MHSanaei
328eeb8233 Update workflows to use Go 1.22 2024-02-20 01:14:53 +03:30
MHSanaei
02239c8f2d [xray] dns - new
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-19 23:34:25 +03:30
MHSanaei
70b3db074a open sniffing for all protocols
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-19 21:05:24 +03:30
MHSanaei
d560cd9cc8 security issue - remove favicon
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-19 21:04:14 +03:30
somebodywashere
7526c4d969 Fix Enabled/Disabled counter (#1847) 2024-02-19 17:58:32 +03:30
Ho3ein
766ef54b31 Update README.md 2024-02-19 11:38:51 +03:30
MHSanaei
6b5535e60a some changes
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-19 00:47:47 +03:30
MHSanaei
fe00cfb09b [xray] option error log 2024-02-18 01:13:39 +03:30
MHSanaei
2b4d6160c4 minor changes 2024-02-18 01:11:43 +03:30
MHSanaei
57029b1a40 prerequisite - tzdata 2024-02-18 01:11:43 +03:30
MHSanaei
61489077d7 [wg] auto multi-peer and qrcode
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-18 00:46:49 +03:30
MHSanaei
4621933e5b [outbound] set master outbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-17 21:40:25 +03:30
MHSanaei
fb76b2d500 [feature] export subs
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-17 21:38:22 +03:30
MHSanaei
9f6957ef3f [logs] new bug-free log_writer
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-17 21:37:58 +03:30
MHSanaei
d6e05d4a1a tgbot - Telegram api 7.1 changes 2024-02-17 21:15:53 +03:30
Shahin
ea67b9760d Minor fix in outbound form (#1810)
* Update outbound.html

* Update outbound.js

* Update outbound.html

* Update outbound.html
2024-02-17 19:53:40 +03:30
Alireza Ahmand
3a503f12c8 Progressive Web App (#1678)
* pwa support

* Create go.yml

* Delete .github/workflows/go.yml
2024-02-17 19:53:22 +03:30
Jalal Saberi
2b7ad7cb9b Update Uninstall Option (#1801)
after uninstall, script will delete itself and show Install & Upgrade command for installing again if user need that.
2024-02-17 19:53:02 +03:30
dependabot[bot]
9f38e19b81 Bump google.golang.org/grpc from 1.61.0 to 1.61.1 (#1812)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.61.0 to 1.61.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 19:52:47 +03:30
Tara Rostami
73718a5dc5 UI improvements (#1813) 2024-02-17 19:52:23 +03:30
dependabot[bot]
bb9d00a0b3 Bump github.com/mymmrac/telego from 0.28.0 to 0.29.0 (#1829)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 19:51:32 +03:30
somebodywashere
3a1be63a40 some log changes (#1789)
* some logs changes

* removed some empty lines
2024-02-10 14:10:39 +03:30
MHSanaei
4daaf0a647 clear log hourly if !j.hasLimitIp and "./access.log" exist 2024-02-10 01:52:20 +03:30
MHSanaei
f5dacd28e1 bash - Firewall Management 2024-02-07 21:23:11 +03:30
dependabot[bot]
f65d3a5a98 Bump gorm.io/gorm from 1.25.7-0.20240204074919-46816ad31dde to 1.25.7 (#1771)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.7-0.20240204074919-46816ad31dde to 1.25.7.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/commits/v1.25.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-07 13:26:13 +03:30
surbiks
13de2c6ca0 add outbound traffic reset (#1767) 2024-02-07 11:25:31 +03:30
MHSanaei
6cf29d5145 fix - Ensure logs are not null in show method #1763 2024-02-06 13:45:01 +03:30
dependabot[bot]
182710b86c Bump gorm.io/driver/sqlite from 1.5.4 to 1.5.5 (#1762)
Bumps [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite) from 1.5.4 to 1.5.5.
- [Commits](https://github.com/go-gorm/sqlite/compare/v1.5.4...v1.5.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-06 13:01:35 +03:30
MHSanaei
4a1387ea83 translate - ID #1759 2024-02-06 12:21:25 +03:30
Saeid
c53cee31f5 Manage balancers in settings UI (#1759)
* add balancer config to ui

* manage balancer in rules table

* fix balancer translations

* fix edit button text
2024-02-06 11:40:49 +03:30
MHSanaei
222b9734ca Lang - Indonesian #1710
Co-Authored-By: Muhamad Solihin <85750131+lihin929@users.noreply.github.com>
2024-02-05 12:44:37 +03:30
MHSanaei
c9ba393ce7 xray config - statsOutbound 2024-02-04 13:02:28 +03:30
Ho3ein
aa40016ec8 Merge branch 'main' into main 2024-02-04 02:34:39 +03:30
MHSanaei
dc49304aa5 Update README.md 2024-02-04 02:01:55 +03:30
MHSanaei
bb7b667467 v2.1.3 2024-02-04 01:51:43 +03:30
MHSanaei
d171850255 IPLimit - IPv4 Extraction Simplification 2024-02-04 01:51:31 +03:30
MHSanaei
1207501405 tgbot - improve translate 2024-02-04 01:51:01 +03:30
MHSanaei
2a2bf531ee Fix tgbot - document upload issue for empty ban logs 2024-02-04 01:50:14 +03:30
MHSanaei
9d724d34e1 fix tgbot - no warning for empty socks5 2024-02-04 01:45:55 +03:30
MHSanaei
618a566283 new - select option for loglevel & access log 2024-02-04 01:45:55 +03:30
MHSanaei
6804facabc bug fix log_writer
+ notice log level

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-02-04 01:45:16 +03:30
Mehdi Khodayari
98dd6bb949 This modification uses a Scanner to read the file line by line, which can be more memory-efficient for large files. (#1736) 2024-02-03 14:11:57 +03:30
dependabot[bot]
f0e9aa0b8f Bump github.com/shirou/gopsutil/v3 from 3.23.12 to 3.24.1 (#1725)
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.12 to 3.24.1.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.12...v3.24.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 17:15:41 +03:30
dependabot[bot]
68a16ef0e2 Bump docker/metadata-action from 5.5.0 to 5.5.1 (#1726)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.5.0 to 5.5.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.5.0...v5.5.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 17:15:17 +03:30
dependabot[bot]
012775833a Bump github.com/nicksnyder/go-i18n/v2 from 2.3.0 to 2.4.0 (#1721)
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.3.0...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 22:56:57 +03:30
MHSanaei
e4567a2b24 host name for ws header req 2024-01-30 00:28:03 +03:30
Saeid
6c0775b120 Show outbound traffic in outbounds table (#1711)
* store outbound traffic in database

* show outbound traffic in outbounds table

* add refresh button
2024-01-30 00:07:20 +03:30
emirjorge
9fbaede59f Fix Spanish Translation in Main tittle (#1699)
* Add files via upload

* Update translate.es_ES.toml
2024-01-30 00:03:51 +03:30
Saeid
fd75cca266 fix bug in edit SOCKS and HTTP outbound (#1704) 2024-01-30 00:02:58 +03:30
MHSanaei
9f904f8f47 warp - ForceIP
ForceIPv6v4 to ForceIP
2024-01-29 23:36:21 +03:30
MHSanaei
78e1194ebb bug fix - traffic limit tbbot 2024-01-29 23:36:03 +03:30
MHSanaei
e04283c1fb remove multi protocol script 2024-01-27 19:21:16 +03:30
MHSanaei
a6742f395a remove multi protocol script 2024-01-27 15:13:12 +03:30
MHSanaei
9fba92d879 v2.1.2
revert #1650 #1661 #1664 #1670
made panel full of bug
2024-01-27 00:32:19 +03:30
MHSanaei
daa4354047 ipv6 for familyProtect and some changes 2024-01-26 21:57:46 +03:30
dependabot[bot]
ec88053df0 Bump gorm.io/gorm from 1.25.5 to 1.25.6 (#1683)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.5 to 1.25.6.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.5...v1.25.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-26 20:15:07 +03:30
MHSanaei
08e259327b unnecessary use of fmt.Sprintf 2024-01-26 17:06:54 +03:30
MHSanaei
98384ac236 fix - direct tag 2024-01-26 16:50:50 +03:30
MHSanaei
5f9058c84f fix downloaded log format
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-26 16:50:22 +03:30
MHSanaei
979fdedbbe [bug] avoid empty inbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-24 20:50:58 +03:30
dependabot[bot]
2463b99479 Bump google.golang.org/grpc from 1.60.1 to 1.61.0 (#1671)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.1 to 1.61.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.60.1...v1.61.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-24 15:13:53 +03:30
MHSanaei
ff547a258d gen button - publicKey & psk 2024-01-24 01:24:17 +03:30
Ali Rahimi
251ceeedba bug fixed (import inbounds) (#1670)
* add single client bug fixed

* bug fixed
2024-01-24 01:23:15 +03:30
Ali Rahimi
c1422be269 persian datepicker bug fixed (#1668)
* add single client bug fixed

* persian datepicker bug fixed
2024-01-23 23:16:33 +03:30
Ali Rahimi
538fc9b365 add single client bug fixed (#1664) 2024-01-23 13:30:21 +03:30
Ali Rahimi
b172d450e3 Group editing feature of users with the same subscription (#1661) 2024-01-22 15:08:36 +03:30
Ali Rahimi
5c695ca652 add group user with the same subscription id to all inbounds (#1650) 2024-01-21 17:56:19 +03:30
MHSanaei
e7ce8c8ddb minor changes
disAllowedIps doesn't show on debug if there is no ip
change copy to clipboard to export inbound
2024-01-21 14:39:15 +03:30
MHSanaei
4b9bbbc34b clear banned log after unban everyone 2024-01-21 04:45:17 +03:30
MHSanaei
820e302aac revert fail2ban v1.0.2
it doesn't work well
2024-01-21 04:00:58 +03:30
quydang
ebaabf6150 Update README (#1648) 2024-01-20 20:11:58 +03:30
MHSanaei
a7bea936c5 Update x-ui.sh 2024-01-20 19:46:10 +03:30
MHSanaei
38378fe36f update fail2ban v1.0.2 for debian 2024-01-20 17:02:16 +03:30
MHSanaei
7f13adbd05 fail2ban - auto allowipv6 2024-01-20 15:49:34 +03:30
quydang
74ba6d7825 Add support for ARMv5 and x86 (#1642) 2024-01-20 13:02:35 +03:30
MHSanaei
7fd4015f17 Update x-ui.sh
Fail2ban installation failed
2024-01-19 18:28:09 +03:30
Shahin
589a8702aa Revert-back some edits (#1617)
* Update index.html

* Update translate.en_US.toml
2024-01-18 10:30:38 +03:30
emirjorge
91af54abf1 Fix Spanish Translation (#1619) 2024-01-18 10:29:43 +03:30
MHSanaei
82244ced73 v2.1.1 2024-01-17 21:10:06 +03:30
Nguyễn Cao Nguyên
6856807726 Normalize VN translation (#1607)
Fixed some translations that were too hard to understand.
2024-01-17 20:13:49 +03:30
MHSanaei
2488adc042 remove old files, codes, info 2024-01-17 17:09:29 +03:30
Amin
62f08e877d Fix inbounds with the same port and different IPs (#1595) 2024-01-17 16:21:28 +03:30
somebodywashere
3ed6fc4036 Quick fix RU ,ZH,FA tgbot.messages.active (#1596) 2024-01-17 16:18:21 +03:30
quydang
6550aa6bad Hotfix for ARMv7 and ARMv6 (#1589) 2024-01-16 03:31:15 +03:30
MHSanaei
2a20243751 v2.1.0 2024-01-16 02:01:36 +03:30
MHSanaei
fc56a1acac fix syntax error - workflow 2024-01-15 18:37:07 +03:30
shahin-io
679dc69f6e Update README.md (#1584) 2024-01-15 14:03:45 +03:30
quydang
ca2b3dc4fc Support ARMv6 (#1582) 2024-01-15 13:44:13 +03:30
shahin-io
c3d90c3f94 translate enhancement (#1574) 2024-01-15 13:07:01 +03:30
MHSanaei
98cf1f2db6 [bug] fix switch enable inbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-12 19:19:32 +03:30
shahin-io
4b5dd99555 Small edit & fixes (#1571) 2024-01-12 17:55:18 +03:30
quydang
491f7aecef A few updates (#1566)
* Update install.sh

* Update x-ui.sh

* Update README.md
2024-01-12 11:45:46 +03:30
MHSanaei
590a8f07b9 wireguard info page
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-11 18:54:18 +03:30
MHSanaei
594f004d2a better view for http header 2024-01-11 18:11:48 +03:30
MHSanaei
bee690429f WARP via wireguard
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-11 09:57:21 +03:30
MHSanaei
2111632702 [ui] separate outbound and reverse
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-11 09:30:37 +03:30
MHSanaei
a9229ecafe [ui] user settings
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-11 09:24:28 +03:30
shahin-io
dadfa107af Update translate.en_US.toml (#1555) 2024-01-11 09:23:26 +03:30
shahin-io
6feff78968 Update index.html (#1557) 2024-01-11 09:23:09 +03:30
MHSanaei
f035837aed Centralized Xray URLs 2024-01-10 22:13:30 +03:30
MHSanaei
b056c2dda0 fix mistake - xtls 2024-01-10 20:41:43 +03:30
MHSanaei
97e6420018 fix typo 2024-01-10 20:41:24 +03:30
MHSanaei
abfcbf4226 reset button for kcp & quic 2024-01-10 17:49:53 +03:30
MHSanaei
1f9b3730d4 fix log writer crash
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-10 16:16:18 +03:30
MHSanaei
0824512a46 fix translation
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-10 16:14:37 +03:30
MHSanaei
ee703ad857 wireguard outbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-10 16:14:18 +03:30
MHSanaei
722f5e716f [feature] wireguard inbound
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-10 16:12:54 +03:30
MHSanaei
fdf31d80e7 Update dependencies and Xray v1.8.7 2024-01-09 17:45:10 +03:30
MHSanaei
f8fccc057b fix switchEnable in filter mode
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-09 16:44:22 +03:30
MHSanaei
4bb31b0af4 [bug] fix tcp http header version
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-01-09 16:43:24 +03:30
MHSanaei
8b25e21f4e bug fix #1524 2024-01-09 16:39:22 +03:30
shahin-io
984b469c6f Overall Enhancement (#1524) 2024-01-09 12:40:40 +03:30
dependabot[bot]
96c4cfeb23 Bump github.com/xtls/xray-core from 1.8.6 to 1.8.7 (#1537)
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.6 to 1.8.7.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.6...v1.8.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:12:47 +03:30
dependabot[bot]
e50b8db6c5 Bump docker/metadata-action from 5.4.0 to 5.5.0 (#1536)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.4.0...v5.5.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 13:06:52 +03:30
shahin-io
0734f4cd54 Update translate.en_US.toml (#1522)
Dear @MHSanaei 

I've made a quick and small edit. I'll make more enhancements in the coming weeks.
2024-01-06 17:55:48 +03:30
shahin-io
fbab0df504 Update README.md (#1521)
hello, Dear @MHSanaei 

I've made overall enhancements.
2024-01-06 12:06:59 +03:30
Saeid
5e3478f1c1 socks5 proxy option added to telegram bot settings (#1500)
* socks5 option added to telegram bot settings

* update socks5 proxy settings translations
2024-01-03 16:29:29 +03:30
Ali Rahimi
c76199514a added Jalalian datepicker (shamsi) (#1460)
* added datepicker option in setting page
jalalian datepicker component was added
translate files for datepicker updated

* dark mode bug fixed
2024-01-02 12:02:21 +03:30
Mammad
31e9734414 bash - Multi protocol support (#1496) 2024-01-01 23:47:50 +03:30
somebodywashere
ceee1e4277 Major changes to tgbot, also small changes for panel (#1463)
* Reduce outage time on Xray errors

* Improved logs clearing, added previous logs
File name change: 3xipl-access-persistent.log -> 3xipl-ap.log
All previous logs have .prev suffix

* Preparations for tgbot additions

* [tgbot] Improvements, Additions and Fixes
* Changed interaction with Expire Date for Clients
* Added more info and interactions with Online Clients
* Added a way to get Ban Logs (also added them to backup)
* Few fixes and optimizations in code
* Fixed RU translation

* [tgbot] More updates and fixes

* [tgbot] Quick Fix

* [tgbot] Quick Fix 2

* [tgbot] Big Updates
Added Notifications for Clients throught Tgbot (when Expire)
Added compability for Usernames both w/wo @
Added more buttons overall for admins

* [tgbot] Fixes

* [tbot] Fixes 2

* [tgbot] Removed usernames support for Notifications to work

* [tgbot] Fix

* [tgbot] Fix Notify

* [tgbot] small fixes

* [tgbot] replyMarkup only for last message on big messages

* [tgbot] Fixed last message is empty

* [tgbot] Fix messages split
2024-01-01 18:37:56 +03:30
Serge Pavlyuk
b725ea7de5 Outboud wireguard protocol support (#1451)
* Wireguard outbound settings modal window

* wireguard optional fields saniteze fix

* wireguard save domainStrategy and reserved(not implemented in form but will work)

---------

Co-authored-by: Сергей Павлюк <spavlyuk@nic.ru>
2024-01-01 16:33:43 +03:30
dependabot[bot]
0a4c8ffcf5 Bump github.com/shirou/gopsutil/v3 from 3.23.11 to 3.23.12 (#1492)
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.11 to 3.23.12.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.11...v3.23.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 16:22:20 +03:30
vuong2023
d305dd4e9f Update translate.vi_VN.toml (#1478)
fix: Correct errors in Vietnamese sentences
2024-01-01 16:22:10 +03:30
MHSanaei
bbcab768ca edit languages 2023-12-24 10:50:15 +03:30
MHSanaei
954bf6fb5d tcp header - set name to host 2023-12-23 23:19:10 +03:30
MHSanaei
77b83d81e2 Block Malware, Phishing and Cryptominers Websites 2023-12-23 23:03:33 +03:30
Ho3ein
10cd5159d1 Update x-ui.sh (#1437) 2023-12-23 18:10:51 +03:30
MHSanaei
b8b2acf853 Update README.md 2023-12-23 16:40:18 +03:30
MHSanaei
4f3b93171a fix outbound socks/http
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-12-23 16:37:32 +03:30
MHSanaei
9261f9c665 fix protocol in routing rules modal
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-12-23 16:37:09 +03:30
quydang
6a41e19f7a Update README and added support for AlmaLinux. (#1435)
* Update install.sh

* Update x-ui.sh

* Update install.sh

* Update README.md

* Update install.sh
2023-12-23 12:26:56 +03:30
Jiraiya
0d2bdde149 Fix Chinese translation (#1428)
Co-authored-by: qingbo <qingbo@jingling.group>
2023-12-22 14:09:05 +03:30
Tara Rostami
bcfa47e2ad Minor Fixes (#1421)
* Update login.html

* Update custom.css

* Update translate.fa_IR.toml

* Update translate.fa_IR.toml

* Update translate.en_US.toml
2023-12-21 20:38:44 +03:30
Hamidreza
77776e1a62 [fix] redirect url
it didn't redirect to the correct port !!!
2023-12-21 15:39:09 +03:30
MHSanaei
c0d8f931da v2.0.2 2023-12-19 14:43:00 +03:30
103 changed files with 9120 additions and 2990 deletions

View File

@@ -1,4 +1,4 @@
name: Release X-ui dockerhub name: Release 3X-UI for Docker
on: on:
push: push:
tags: tags:
@@ -18,7 +18,7 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0 uses: docker/setup-buildx-action@v3.0.0
- name: Log in to GitHub Container Registry - name: Login to GHCR
uses: docker/login-action@v3.0.0 uses: docker/login-action@v3.0.0
with: with:
registry: ghcr.io registry: ghcr.io
@@ -27,7 +27,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5.4.0 uses: docker/metadata-action@v5.5.1
with: with:
images: ghcr.io/${{ github.repository }} images: ghcr.io/${{ github.repository }}
@@ -36,6 +36,6 @@ jobs:
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7 platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,4 +1,4 @@
name: Release 3X-ui name: Release 3X-UI
on: on:
push: push:
@@ -10,7 +10,13 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
platform: [amd64, arm64, arm] platform:
- amd64
- arm64
- armv7
- armv6
- 386
- armv5
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -19,15 +25,21 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5.0.0 uses: actions/setup-go@v5.0.0
with: with:
go-version: '1.21' go-version: '1.22'
- name: Install dependencies for arm64 and arm - name: Install dependencies
if: matrix.platform == 'arm64' || matrix.platform == 'arm'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt install gcc-aarch64-linux-gnu if [ "${{ matrix.platform }}" == "arm64" ]; then
if [ "${{ matrix.platform }}" == "arm" ]; then sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "armv6" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
elif [ "${{ matrix.platform }}" == "armv5" ]; then
sudo apt install gcc-arm-linux-gnueabi
fi fi
- name: Build x-ui - name: Build x-ui
@@ -36,9 +48,23 @@ jobs:
export GOOS=linux export GOOS=linux
export GOARCH=${{ matrix.platform }} export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "arm" ]; then elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "armv6" ]; then
export GOARCH=arm
export GOARM=6
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv5" ]; then
export GOARCH=arm
export GOARM=5
export CC=arm-linux-gnueabi-gcc
fi fi
go build -o xui-release -v main.go go build -o xui-release -v main.go
@@ -51,20 +77,33 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
if [ "${{ matrix.platform }}" == "amd64" ]; then if [ "${{ matrix.platform }}" == "amd64" ]; then
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-64.zip wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-arm64-v8a.zip wget ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip rm -f Xray-linux-arm64-v8a.zip
else elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-arm32-v7a.zip wget ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "armv6" ]; then
wget ${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
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
elif [ "${{ matrix.platform }}" == "armv5" ]; then
wget ${Xray_URL}Xray-linux-arm32-v5.zip
unzip Xray-linux-arm32-v5.zip
rm -f Xray-linux-arm32-v5.zip
fi fi
rm -f geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat 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/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.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 geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
@@ -77,12 +116,11 @@ jobs:
- name: Package - name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- name: Upload - name: Upload files to GH release
uses: svenstaro/upload-release-action@2.7.0 uses: svenstaro/upload-release-action@2.9.0
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }} tag: ${{ github.ref }}
file: x-ui-linux-${{ matrix.platform }}.tar.gz file: x-ui-linux-${{ matrix.platform }}.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
prerelease: true prerelease: true
overwrite: true

View File

@@ -1,10 +1,13 @@
#!/bin/sh #!/bin/sh
case $1 in case $1 in
amd64) amd64)
ARCH="64" ARCH="64"
FNAME="amd64" FNAME="amd64"
;; ;;
i386)
ARCH="32"
FNAME="i386"
;;
armv8 | arm64 | aarch64) armv8 | arm64 | aarch64)
ARCH="arm64-v8a" ARCH="arm64-v8a"
FNAME="arm64" FNAME="arm64"
@@ -13,23 +16,25 @@ case $1 in
ARCH="arm32-v7a" ARCH="arm32-v7a"
FNAME="arm32" FNAME="arm32"
;; ;;
armv6)
ARCH="arm32-v6"
FNAME="armv6"
;;
*) *)
ARCH="64" ARCH="64"
FNAME="amd64" FNAME="amd64"
;; ;;
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.7/Xray-linux-${ARCH}.zip"
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget 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 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 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 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 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 -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
cd ../../

View File

@@ -1,11 +1,9 @@
# ======================================================== # ========================================================
# Stage: Builder # Stage: Builder
# ======================================================== # ========================================================
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder FROM golang:1.22-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN apk --no-cache --update add \ RUN apk --no-cache --update add \
build-base \ build-base \
@@ -15,6 +13,8 @@ RUN apk --no-cache --update add \
COPY . . COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go RUN go build -o build/x-ui main.go
RUN ./DockerInit.sh "$TARGETARCH" RUN ./DockerInit.sh "$TARGETARCH"
@@ -28,11 +28,13 @@ WORKDIR /app
RUN apk add --no-cache --update \ RUN apk add --no-cache --update \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
fail2ban fail2ban \
bash
COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
# Configure fail2ban # Configure fail2ban
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
@@ -47,4 +49,5 @@ RUN chmod +x \
/usr/bin/x-ui /usr/bin/x-ui
VOLUME [ "/etc/x-ui" ] VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ]
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]

356
README.md
View File

@@ -1,6 +1,6 @@
# 3x-ui # 3X-UI
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment** **An Advanced Web Panel • Built on 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/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)](#) [![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
@@ -8,64 +8,78 @@
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.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) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian,Vietnamese,Spanish)** > **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment
**If you think this project is helpful to you, you may wish to give a** :star2:
**Buy Me a Coffee :** **If this project is helpful to you, you may wish to give it a**:star2:
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` <a href="#">
<img width="125" alt="image" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1.jpg"></a>
# Install & Upgrade - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
## Install & Upgrade
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
``` ```
# Install custom version ## Install Custom Version
To install your desired version you can add the version to the end of install command. Example for ver `v2.0.2`: To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.0`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.0.2 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.0
``` ```
# SSL ## SSL Certificate
<details>
<summary>Click for SSL Certificate</summary>
### Cloudflare
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name has been resolved to the current server through cloudflare
**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
### Certbot
``` ```
apt-get install certbot -y apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run certbot renew --dry-run
``` ```
You also can use `x-ui` menu then select `SSL Certificate Management` ***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
# Features </details>
- System Status Monitoring ## Manual Install & Upgrade
- Search within all inbounds and clients
- Support Dark/Light theme UI
- Support multi-user multi-protocol, web page visualization operation
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
- Support for configuring more transport configurations
- Traffic statistics, limit traffic, limit expiration time
- Customizable xray configuration templates
- Support https access panel (self-provided domain name + ssl certificate)
- Support one-click SSL certificate application and automatic renewal
- For more advanced configuration items, please refer to the panel
- Fix api routes (user setting will create with api)
- Support to change configs by different items provided in panel
- Support export/import database from panel
# Manual Install & Upgrade
<details> <details>
<summary>Click for Manual Install details</summary> <summary>Click for manual install details</summary>
#### Usage
1. To download the latest version of the compressed package directly to your server, run the following command: 1. To download the latest version of the compressed package directly to your server, run the following command:
```sh ```sh
ARCH=$(uname -m) ARCH=$(uname -m)
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" 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" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
``` ```
@@ -73,7 +87,16 @@ wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI
```sh ```sh
ARCH=$(uname -m) ARCH=$(uname -m)
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64" 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" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/ cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
@@ -88,11 +111,13 @@ systemctl restart x-ui
</details> </details>
# Install with Docker ## Install with Docker
<details> <details>
<summary>Click for Docker details</summary> <summary>Click for Docker details</summary>
#### Usage
1. Install Docker: 1. Install Docker:
```sh ```sh
@@ -125,63 +150,131 @@ systemctl restart x-ui
ghcr.io/mhsanaei/3x-ui:latest ghcr.io/mhsanaei/3x-ui:latest
``` ```
</details> update to latest version
# Default settings
<details>
<summary>Click for Default settings details</summary>
- Port: 2053
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
- database path: /etc/x-ui/x-ui.db
- xray config path: /usr/local/x-ui/bin/config.json
Before you set ssl on settings
- http://ip:2053/panel
- http://domain:2053/panel
After you set ssl on settings
- https://yourdomain:2053/panel
</details>
# Xray Configurations:
<details>
<summary>Click for Xray Configurations details</summary>
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
- [traffic](./media/configs/traffic.json)
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
</details>
# [WARP Configuration](https://gitlab.com/fscarmen/warp) (Optional)
<details>
<summary>Click for WARP Configuration details</summary>
If you want to use routing to WARP follow steps as below:
1. If you already installed warp, you can uninstall using below command:
```sh ```sh
warp u cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
``` ```
2. Install WARP on **socks proxy mode**: remove 3x-ui from docker
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## Recommended OS
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rockylinux 9+
## Supported Architectures and Devices
<details>
<summary>Click for Supported Architectures and devices details</summary>
Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
</details>
## Languages
- English
- Farsi
- Chinese
- Russian
- Vietnamese
- Spanish
- Indonesian
## Features
- System Status Monitoring
- Search within all inbounds and clients
- Dark/Light theme
- Supports multi-user and multi-protocol
- Supports protocols, including VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard
- Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY
- Traffic statistics, traffic limit, expiration time limit
- Customizable Xray configuration templates
- Supports HTTPS access panel (self-provided domain name + SSL certificate)
- Supports One-Click SSL certificate application and automatic renewal
- For more advanced configuration items, please refer to the panel
- Fixes API routes (user setting will be created with API)
- Supports changing configs by different items provided in the panel.
- Supports export/import database from the panel
## Default Settings
<details>
<summary>Click for default settings details</summary>
### Information
- **Port:** 2053
- **Username & Password:** It will be generated randomly if you skip modifying.
- **Database Path:**
- /etc/x-ui/x-ui.db
- **Xray Config Path:**
- /usr/local/x-ui/bin/config.json
- **Web Panel Path w/o Deploying SSL:**
- http://ip:2053/panel
- http://domain:2053/panel
- **Web Panel Path w/ Deploying SSL:**
- https://domain:2053/panel
</details>
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
<details>
<summary>Click for WARP configuration details</summary>
#### Usage
If you want to use routing to WARP before v2.1.0 follow steps as below:
**1.** Install WARP on **SOCKS Proxy Mode**:
```sh ```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
``` ```
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json) **2.** If you already installed warp, you can uninstall using below command:
```sh
warp u
```
**3.** Turn on the config you need in panel
Config Features: Config Features:
@@ -191,12 +284,14 @@ If you want to use routing to WARP follow steps as below:
</details> </details>
# IP Limit ## IP Limit
<details> <details>
<summary>Click for IP Limit details</summary> <summary>Click for IP limit details</summary>
**Note: IP Limit won't work correctly when using IP Tunnel** #### Usage
**Note:** IP Limit won't work correctly when using IP Tunnel
- For versions up to `v1.6.1`: - For versions up to `v1.6.1`:
@@ -210,44 +305,46 @@ If you want to use routing to WARP follow steps as below:
2. Select `IP Limit Management`. 2. Select `IP Limit Management`.
3. Choose the appropriate options based on your needs. 3. Choose the appropriate options based on your needs.
- make sure you have access.log on your Xray Configuration - make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
```sh ```sh
"log": { "log": {
"loglevel": "warning",
"access": "./access.log", "access": "./access.log",
"error": "./error.log" "dnsLog": false,
"loglevel": "warning"
}, },
``` ```
</details> </details>
# Telegram Bot ## Telegram Bot
<details> <details>
<summary>Click for Telegram Bot details</summary> <summary>Click for Telegram bot details</summary>
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html) #### Usage
Set the robot-related parameters in the panel background, including:
- Tg robot Token The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including:
- Tg robot ChatId
- Tg robot cycle runtime, in crontab syntax
- Tg robot Expiration threshold
- Tg robot Traffic threshold
- Tg robot Enable send backup in cycle runtime
- Tg robot Enable CPU usage alarm threshold
Reference syntax: - Telegram Token
- Admin Chat ID(s)
- Notification Time (in cron syntax)
- Expiration Date Notification
- Traffic Cap Notification
- Database Backup
- CPU Load Notification
- 30 \* \* \* \* \* //Notify at the 30s of each point
- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
- @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning)
- @weekly // weekly notification
- @every 8h // notify every 8 hours
# Telegram Bot Features **Reference syntax:**
- `30 \* \* \* \* \*` - Notify at the 30s of each point
- `0 \*/10 \* \* \* \*` - Notify at the first second of each 10 minutes
- `@hourly` - Hourly notification
- `@daily` - Daily notification (00:00 in the morning)
- `@weekly` - weekly notification
- `@every 8h` - Notify every 8 hours
### Telegram Bot Features
- Report periodic - Report periodic
- Login notification - Login notification
@@ -262,9 +359,8 @@ Reference syntax:
- Check depleted users - Check depleted users
- Receive backup by request and in periodic reports - Receive backup by request and in periodic reports
- Multi language bot - Multi language bot
</details>
# Setting up Telegram bot ### Setting up Telegram bot
- Start [Botfather](https://t.me/BotFather) in your Telegram account: - Start [Botfather](https://t.me/BotFather) in your Telegram account:
![Botfather](./media/botfather.png) ![Botfather](./media/botfather.png)
@@ -284,12 +380,15 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID. - How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID.
![User ID](./media/user-id.png) ![User ID](./media/user-id.png)
</details>
# API routes ## API Routes
<details> <details>
<summary>Click for API routes details</summary> <summary>Click for API routes details</summary>
#### Usage
- `/login` with `POST` user data: `{username: '', password: ''}` for login - `/login` with `POST` user data: `{username: '', password: ''}` for login
- `/panel/api/inbounds` base for following actions: - `/panel/api/inbounds` base for following actions:
@@ -324,10 +423,12 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9) - [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details> </details>
# Environment Variables ## Environment Variables
<details> <details>
<summary>Click for Environment Variables details</summary> <summary>Click for environment variables details</summary>
#### Usage
| Variable | Type | Default | | Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ | | -------------- | :--------------------------------------------: | :------------ |
@@ -345,27 +446,7 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
</details> </details>
# A Special Thanks To ## Preview
- [alireza0](https://github.com/alireza0/)
# Acknowledgment
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
# Suggestion System
- Ubuntu 20.04+
- Debian 10+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian (for ARM devices)
# Pictures
![1](./media/1.png) ![1](./media/1.png)
![2](./media/2.png) ![2](./media/2.png)
@@ -375,6 +456,15 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
![6](./media/6.png) ![6](./media/6.png)
![7](./media/7.png) ![7](./media/7.png)
## Stargazers over time ## A Special Thanks to
- [alireza0](https://github.com/alireza0/)
## 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._
## 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)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1 +1 @@
2.0.2 2.2.0

View File

@@ -21,6 +21,7 @@ var db *gorm.DB
var initializers = []func() error{ var initializers = []func() error{
initUser, initUser,
initInbound, initInbound,
initOutbound,
initSetting, initSetting,
initInboundClientIps, initInboundClientIps,
initClientTraffic, initClientTraffic,
@@ -51,6 +52,10 @@ func initInbound() error {
return db.AutoMigrate(&model.Inbound{}) return db.AutoMigrate(&model.Inbound{})
} }
func initOutbound() error {
return db.AutoMigrate(&model.OutboundTraffics{})
}
func initSetting() error { func initSetting() error {
return db.AutoMigrate(&model.Setting{}) return db.AutoMigrate(&model.Setting{})
} }

View File

@@ -37,13 +37,22 @@ type Inbound struct {
// config part // config part
Listen string `json:"listen" form:"listen"` Listen string `json:"listen" form:"listen"`
Port int `json:"port" form:"port" gorm:"unique"` Port int `json:"port" form:"port"`
Protocol Protocol `json:"protocol" form:"protocol"` Protocol Protocol `json:"protocol" form:"protocol"`
Settings string `json:"settings" form:"settings"` Settings string `json:"settings" form:"settings"`
StreamSettings string `json:"streamSettings" form:"streamSettings"` StreamSettings string `json:"streamSettings" form:"streamSettings"`
Tag string `json:"tag" form:"tag" gorm:"unique"` Tag string `json:"tag" form:"tag" gorm:"unique"`
Sniffing string `json:"sniffing" form:"sniffing"` Sniffing string `json:"sniffing" form:"sniffing"`
} }
type OutboundTraffics struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Tag string `json:"tag" form:"tag" gorm:"unique"`
Up int64 `json:"up" form:"up" gorm:"default:0"`
Down int64 `json:"down" form:"down" gorm:"default:0"`
Total int64 `json:"total" form:"total" gorm:"default:0"`
}
type InboundClientIps struct { type InboundClientIps struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"` Id int `json:"id" gorm:"primaryKey;autoIncrement"`
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`

73
go.mod
View File

@@ -1,71 +1,72 @@
module x-ui module x-ui
go 1.21.4 go 1.22.0
require ( require (
github.com/Calidity/gin-sessions v1.3.1 github.com/Calidity/gin-sessions v1.3.1
github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.2
github.com/mymmrac/telego v0.28.0 github.com/mymmrac/telego v0.29.1
github.com/nicksnyder/go-i18n/v2 v2.3.0 github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.1.1 github.com/pelletier/go-toml/v2 v2.1.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.11 github.com/shirou/gopsutil/v3 v3.24.1
github.com/xtls/xray-core v1.8.6 github.com/valyala/fasthttp v1.52.0
github.com/xtls/xray-core v1.8.7
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
google.golang.org/grpc v1.60.1 google.golang.org/grpc v1.62.0
gorm.io/driver/sqlite v1.5.4 gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.7
) )
require ( require (
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect github.com/bytedance/sonic v1.10.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudflare/circl v1.3.6 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.4.22 // indirect github.com/fasthttp/router v1.4.22 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/go-playground/validator/v10 v10.17.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect
github.com/grbit/go-json v0.11.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mattn/go-sqlite3 v1.14.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.13.1 // indirect github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.0 // indirect github.com/quic-go/quic-go v0.40.1 // indirect
github.com/refraction-networking/utls v1.5.4 // indirect github.com/refraction-networking/utls v1.6.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.2.17 // indirect github.com/sagernet/sing v0.3.0 // indirect
github.com/sagernet/sing-shadowsocks v0.2.5 // indirect github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
@@ -75,25 +76,25 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/mock v0.3.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.6.0 // indirect golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.4.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.15.0 // indirect golang.org/x/tools v0.16.1 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
lukechampine.com/blake3 v1.2.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect

149
go.sum
View File

@@ -12,8 +12,8 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE= github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns= github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
@@ -30,8 +30,8 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -49,8 +49,6 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
@@ -78,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -109,21 +107,25 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@@ -136,8 +138,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -156,15 +158,15 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
@@ -175,14 +177,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE= github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg= github.com/mymmrac/telego v0.29.1/go.mod h1:ZLD1+L2TQRr97NPOCoN1V2w8y9kmFov33OfZ3qT8cF4=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@@ -208,10 +210,10 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw= github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -221,17 +223,17 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI= github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY= github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A= github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -288,8 +290,10 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U= github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
@@ -299,18 +303,18 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8= github.com/xtls/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U= github.com/xtls/xray-core v1.8.7/go.mod h1:9rFpflfQbgFeH1VKJw7yUmEy7myOyDCgNXXl0bmmyOo=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -319,11 +323,11 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -338,8 +342,8 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -349,8 +353,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -369,8 +373,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -380,20 +385,20 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -406,19 +411,19 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -434,10 +439,10 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=

View File

@@ -26,11 +26,15 @@ echo "The OS release is: $release"
arch3xui() { arch3xui() {
case "$(uname -m)" in case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;; x86_64 | x64 | amd64) echo 'amd64' ;;
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;; armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm | arm32 ) echo 'arm32' ;; armv7* | armv7 | arm) echo 'armv7' ;;
armv6* | armv6) echo 'armv6' ;;
armv5* | armv5) echo 'armv5' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;; *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac esac
} }
echo "arch: $(arch3xui)" echo "arch: $(arch3xui)"
os_version="" os_version=""
@@ -42,17 +46,27 @@ if [[ "${release}" == "centos" ]]; then
fi fi
elif [[ "${release}" == "ubuntu" ]]; then elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1 echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "fedora" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then if [[ ${os_version} -lt 36 ]]; then
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1 echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then if [[ ${os_version} -lt 11 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "almalinux" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use AlmaLinux 9 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "rocky" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use RockyLinux 9 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "arch" ]]; then elif [[ "${release}" == "arch" ]]; then
echo "Your OS is ArchLinux" echo "Your OS is ArchLinux"
@@ -67,19 +81,21 @@ fi
install_base() { install_base() {
case "${release}" in case "${release}" in
centos|fedora) centos | almalinux | rocky)
yum -y update && yum install -y -q wget curl tar yum -y update && yum install -y -q wget curl tar tzdata
;; ;;
arch|manjaro) fedora)
pacman -Syu && pacman -Syu --noconfirm wget curl tar dnf -y update && dnf install -y -q wget curl tar tzdata
;; ;;
*) arch | manjaro)
apt-get update && apt-get upgrade -y && apt install -y -q wget curl tar pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;; ;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
;;
esac esac
} }
# This function will be called when user installed x-ui out of security # This function will be called when user installed x-ui out of security
config_after_install() { config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
@@ -107,9 +123,9 @@ config_after_install() {
echo -e "${green}username:${usernameTemp}${plain}" echo -e "${green}username:${usernameTemp}${plain}"
echo -e "${green}password:${passwordTemp}${plain}" echo -e "${green}password:${passwordTemp}${plain}"
echo -e "###############################################" echo -e "###############################################"
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}" echo -e "${red}if you forgot your login info,you can type x-ui and then type 8 to check after installation${plain}"
else else
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}" echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 8 to check${plain}"
fi fi
fi fi
/usr/local/x-ui/x-ui migrate /usr/local/x-ui/x-ui migrate
@@ -133,7 +149,7 @@ install_x-ui() {
else else
last_version=$1 last_version=$1
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz" url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch3xui).tar.gz"
echo -e "Begining to install x-ui $1" echo -e "Beginning to install x-ui $1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url} wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch3xui).tar.gz ${url}
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}" echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"
@@ -149,18 +165,21 @@ install_x-ui() {
tar zxvf x-ui-linux-$(arch3xui).tar.gz tar zxvf x-ui-linux-$(arch3xui).tar.gz
rm x-ui-linux-$(arch3xui).tar.gz -f rm x-ui-linux-$(arch3xui).tar.gz -f
cd x-ui cd x-ui
chmod +x x-ui
# Check the system's architecture and rename the file accordingly
if [[ $(arch3xui) == "armv5" || $(arch3xui) == "armv6" || $(arch3xui) == "armv7" ]]; then
mv bin/xray-linux-$(arch3xui) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch3xui) chmod +x x-ui bin/xray-linux-$(arch3xui)
cp -f x-ui.service /etc/systemd/system/ cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh chmod +x /usr/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
config_after_install config_after_install
#echo -e "If it is a new installation, the default web port is ${green}2053${plain}, The username and password are ${green}admin${plain} by default"
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 2053 has been released${plain}"
# echo -e "If you want to modify the 2053 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
#echo -e ""
#echo -e "If it is updated panel, access the panel in your previous way"
#echo -e ""
systemctl daemon-reload systemctl daemon-reload
systemctl enable x-ui systemctl enable x-ui
systemctl start x-ui systemctl start x-ui

View File

@@ -65,6 +65,16 @@ func Infof(format string, args ...interface{}) {
addToBuffer("INFO", fmt.Sprintf(format, args...)) addToBuffer("INFO", fmt.Sprintf(format, args...))
} }
func Notice(args ...interface{}) {
logger.Notice(args...)
addToBuffer("NOTICE", fmt.Sprint(args...))
}
func Noticef(format string, args ...interface{}) {
logger.Noticef(format, args...)
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
}
func Warning(args ...interface{}) { func Warning(args ...interface{}) {
logger.Warning(args...) logger.Warning(args...)
addToBuffer("WARNING", fmt.Sprint(args...)) addToBuffer("WARNING", fmt.Sprint(args...))

View File

@@ -1,97 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "IPv4",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv4"
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
]
},
{
"type": "field",
"outboundTag": "IPv4",
"domain": [
"geosite:google"
]
}
]
},
"stats": {}
}

View File

@@ -1,105 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
},
{
"tag": "WARP",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 40000
}
]
}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
]
},
{
"type": "field",
"outboundTag": "WARP",
"domain": [
"geosite:spotify",
"geosite:netflix",
"geosite:openai",
"geosite:google"
]
}
]
},
"stats": {}
}

View File

@@ -1,84 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "blocked",
"domain": [
"regexp:.*\\.ir$",
"regexp:.*\\.xn--mgba3a4f16a$",
"ext:geosite_IR.dat:ir"
]
}
]
},
"stats": {}
}

View File

@@ -1,76 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": [
"HandlerService",
"LoggerService",
"StatsService"
]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": [
"api"
],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": [
"geoip:private",
"ext:geoip_IR.dat:ir"
]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": [
"bittorrent"
]
}
]
},
"stats": {}
}

View File

@@ -1,65 +0,0 @@
{
"log": {
"loglevel": "warning",
"error": "./error.log"
},
"api": {
"tag": "api",
"services": ["HandlerService", "LoggerService", "StatsService"]
},
"inbounds": [
{
"tag": "api",
"listen": "127.0.0.1",
"port": 62789,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
},
{
"tag": "blocked",
"protocol": "blackhole",
"settings": {}
}
],
"policy": {
"levels": {
"0": {
"statsUserDownlink": true,
"statsUserUplink": true
}
},
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
},
{
"type": "field",
"outboundTag": "blocked",
"ip": ["geoip:private"]
},
{
"type": "field",
"outboundTag": "blocked",
"protocol": ["bittorrent"]
}
]
},
"stats": {}
}

105
sub/default.json Normal file
View File

@@ -0,0 +1,105 @@
{
"dns": {
"tag": "dns_out",
"queryStrategy": "UseIP",
"servers": [
{
"address": "8.8.8.8",
"skipFallback": false
}
]
},
"inbounds": [
{
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"userLevel": 8
},
"sniffing": {
"destOverride": [
"http",
"tls",
"fakedns"
],
"enabled": true
},
"tag": "socks"
},
{
"port": 10809,
"protocol": "http",
"settings": {
"userLevel": 8
},
"tag": "http"
}
],
"log": {
"loglevel": "warning"
},
"outbounds": [
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {
"response": {
"type": "http"
}
}
}
],
"policy": {
"levels": {
"8": {
"connIdle": 300,
"downlinkOnly": 1,
"handshake": 4,
"uplinkOnly": 1
}
},
"system": {
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"network": "tcp,udp",
"balancerTag": "all"
}
],
"balancers": [
{
"tag": "all",
"selector": [
"proxy"
],
"strategy": {
"type": "leastPing"
}
}
]
},
"observatory": {
"probeInterval": "5m",
"probeURL": "https://api.github.com/_private/browser/stats",
"subjectSelector": [
"proxy"
],
"EnableConcurrency": true
},
"stats": {}
}

View File

@@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine := gin.Default() engine := gin.Default()
subPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
subDomain, err := s.settingService.GetSubDomain() subDomain, err := s.settingService.GetSubDomain()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
engine.Use(middleware.DomainValidatorMiddleware(subDomain)) engine.Use(middleware.DomainValidatorMiddleware(subDomain))
} }
g := engine.Group(subPath) LinksPath, err := s.settingService.GetSubPath()
if err != nil {
return nil, err
}
s.sub = NewSUBController(g) JsonPath, err := s.settingService.GetSubJsonPath()
if err != nil {
return nil, err
}
Encrypt, err := s.settingService.GetSubEncrypt()
if err != nil {
return nil, err
}
ShowInfo, err := s.settingService.GetSubShowInfo()
if err != nil {
return nil, err
}
RemarkModel, err := s.settingService.GetRemarkModel()
if err != nil {
RemarkModel = "-ieo"
}
SubUpdates, err := s.settingService.GetSubUpdates()
if err != nil {
SubUpdates = "10"
}
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
if err != nil {
SubJsonFragment = ""
}
g := engine.Group("/")
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
return engine, nil return engine, nil
} }

View File

@@ -3,34 +3,57 @@ package sub
import ( import (
"encoding/base64" "encoding/base64"
"strings" "strings"
"x-ui/web/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type SUBController struct { type SUBController struct {
subService SubService subPath string
settingService service.SettingService subJsonPath string
subEncrypt bool
updateInterval string
subService *SubService
subJsonService *SubJsonService
} }
func NewSUBController(g *gin.RouterGroup) *SUBController { func NewSUBController(
a := &SUBController{} g *gin.RouterGroup,
subPath string,
jsonPath string,
encrypt bool,
showInfo bool,
rModel string,
update string,
jsonFragment string) *SUBController {
a := &SUBController{
subPath: subPath,
subJsonPath: jsonPath,
subEncrypt: encrypt,
updateInterval: update,
subService: NewSubService(showInfo, rModel),
subJsonService: NewSubJsonService(jsonFragment),
}
a.initRouter(g) a.initRouter(g)
return a return a
} }
func (a *SUBController) initRouter(g *gin.RouterGroup) { func (a *SUBController) initRouter(g *gin.RouterGroup) {
g = g.Group("/") gLink := g.Group(a.subPath)
gJson := g.Group(a.subJsonPath)
g.GET("/:subid", a.subs) gLink.GET(":subid", a.subs)
gJson.GET(":subid", a.subJsons)
} }
func (a *SUBController) subs(c *gin.Context) { func (a *SUBController) subs(c *gin.Context) {
subEncrypt, _ := a.settingService.GetSubEncrypt() println(c.Request.Header["User-Agent"][0])
subShowInfo, _ := a.settingService.GetSubShowInfo()
subId := c.Param("subid") subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0] host := strings.Split(c.Request.Host, ":")[0]
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo) subs, header, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 { if err != nil || len(subs) == 0 {
c.String(400, "Error!") c.String(400, "Error!")
} else { } else {
@@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) {
} }
// Add headers // Add headers
c.Writer.Header().Set("Subscription-Userinfo", headers[0]) c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", headers[1]) c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", headers[2]) c.Writer.Header().Set("Profile-Title", subId)
if subEncrypt { if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else { } else {
c.String(200, result) c.String(200, result)
} }
} }
} }
func (a *SUBController) subJsons(c *gin.Context) {
println(c.Request.Header["User-Agent"][0])
subId := c.Param("subid")
host := strings.Split(c.Request.Host, ":")[0]
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
c.String(200, jsonSub)
}
}

355
sub/subJsonService.go Normal file
View File

@@ -0,0 +1,355 @@
package sub
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/json_util"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
)
//go:embed default.json
var defaultJson string
type SubJsonService struct {
fragmanet string
inboundService service.InboundService
SubService
}
func NewSubJsonService(fragment string) *SubJsonService {
return &SubJsonService{
fragmanet: fragment,
}
}
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
inbounds, err := s.SubService.getInboundsBySubId(subId)
if err != nil || len(inbounds) == 0 {
return "", "", err
}
var header string
var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic
var configJson map[string]interface{}
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
}
outbounds := []json_util.RawMessage{}
startIndex := 0
// Prepare Inbounds
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
}
if clients == nil {
continue
}
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil {
inbound.Listen = listen
inbound.Port = port
inbound.StreamSettings = streamSettings
}
}
var subClients []model.Client
for _, client := range clients {
if client.Enable && client.SubID == subId {
subClients = append(subClients, client)
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
}
}
outbound := s.getOutbound(inbound, subClients, host, startIndex)
if outbound != nil {
outbounds = append(outbounds, outbound...)
startIndex += len(outbound)
}
}
if len(outbounds) == 0 {
return "", "", nil
}
// Prepare statistics
for index, clientTraffic := range clientTraffics {
if index == 0 {
traffic.Up = clientTraffic.Up
traffic.Down = clientTraffic.Down
traffic.Total = clientTraffic.Total
if clientTraffic.ExpiryTime > 0 {
traffic.ExpiryTime = clientTraffic.ExpiryTime
}
} else {
traffic.Up += clientTraffic.Up
traffic.Down += clientTraffic.Down
if traffic.Total == 0 || clientTraffic.Total == 0 {
traffic.Total = 0
} else {
traffic.Total += clientTraffic.Total
}
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
traffic.ExpiryTime = 0
}
}
}
if s.fragmanet != "" {
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
}
// Combile outbounds
outbounds = append(outbounds, defaultOutbounds...)
configJson["outbounds"] = outbounds
finalJson, _ := json.MarshalIndent(configJson, "", " ")
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return string(finalJson), header, nil
}
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
var newOutbounds []json_util.RawMessage
stream := s.streamData(inbound.StreamSettings)
externalProxies, ok := stream["externalProxy"].([]interface{})
if !ok || len(externalProxies) == 0 {
externalProxies = []interface{}{
map[string]interface{}{
"forceTls": "same",
"dest": host,
"port": float64(inbound.Port),
},
}
}
delete(stream, "externalProxy")
config_index := startIndex
for _, ep := range externalProxies {
extPrxy := ep.(map[string]interface{})
inbound.Listen = extPrxy["dest"].(string)
inbound.Port = int(extPrxy["port"].(float64))
newStream := stream
switch extPrxy["forceTls"].(string) {
case "tls":
if newStream["security"] != "tls" {
newStream["security"] = "tls"
newStream["tslSettings"] = map[string]interface{}{}
}
case "none":
if newStream["security"] != "none" {
newStream["security"] = "none"
delete(newStream, "tslSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
inbound.StreamSettings = string(streamSettings)
for _, client := range clients {
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
switch inbound.Protocol {
case "vmess", "vless":
newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, client))
}
config_index += 1
}
}
return newOutbounds
}
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
var streamSettings map[string]interface{}
json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string)
if security == "tls" {
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
} else if security == "reality" {
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
}
delete(streamSettings, "sockopt")
if s.fragmanet != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
}
// remove proxy protocol
network, _ := streamSettings["network"].(string)
switch network {
case "tcp":
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
case "ws":
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
}
return streamSettings
}
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
netSettings, ok := setting.(map[string]interface{})
if ok {
delete(netSettings, "acceptProxyProtocol")
}
return netSettings
}
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
tlsData := make(map[string]interface{}, 1)
tlsClientSettings := tData["settings"].(map[string]interface{})
tlsData["serverName"] = tData["serverName"]
tlsData["alpn"] = tData["alpn"]
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
tlsData["allowInsecure"] = allowInsecure
}
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
tlsData["fingerprint"] = fingerprint
}
return tlsData
}
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
rltyData := make(map[string]interface{}, 1)
rltyClientSettings := rData["settings"].(map[string]interface{})
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)
shortIds, ok := rData["shortIds"].([]interface{})
if ok && len(shortIds) > 0 {
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
} else {
rltyData["shortId"] = ""
}
serverNames, ok := rData["serverNames"].([]interface{})
if ok && len(serverNames) > 0 {
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
} else {
rltyData["serverName"] = ""
}
return rltyData
}
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
outbound := Outbound{}
usersData := make([]UserVnext, 1)
usersData[0].ID = client.ID
usersData[0].Level = 8
if inbound.Protocol == model.VLESS {
usersData[0].Flow = client.Flow
usersData[0].Encryption = "none"
}
vnextData := make([]VnextSetting, 1)
vnextData[0] = VnextSetting{
Address: inbound.Listen,
Port: inbound.Port,
Users: usersData,
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
outbound.Settings = OutboundSettings{
Vnext: vnextData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
outbound := Outbound{}
serverData := make([]ServerSetting, 1)
serverData[0] = ServerSetting{
Address: inbound.Listen,
Port: inbound.Port,
Level: 8,
Password: client.Password,
}
if inbound.Protocol == model.Shadowsocks {
var inboundSettings map[string]interface{}
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
method, _ := inboundSettings["method"].(string)
serverData[0].Method = method
// server password in multi-user 2022 protocols
if strings.HasPrefix(method, "2022") {
if serverPassword, ok := inboundSettings["password"].(string); ok {
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
}
}
}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
outbound.Settings = OutboundSettings{
Servers: serverData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux map[string]interface{} `json:"mux,omitempty"`
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
Settings OutboundSettings `json:"settings,omitempty"`
}
type OutboundSettings struct {
Vnext []VnextSetting `json:"vnext,omitempty"`
Servers []ServerSetting `json:"servers,omitempty"`
}
type VnextSetting struct {
Address string `json:"address"`
Port int `json:"port"`
Users []UserVnext `json:"users"`
}
type UserVnext struct {
Encryption string `json:"encryption,omitempty"`
Flow string `json:"flow,omitempty"`
ID string `json:"id"`
Level int `json:"level"`
}
type ServerSetting struct {
Password string `json:"password"`
Level int `json:"level"`
Address string `json:"address"`
Port int `json:"port"`
Flow string `json:"flow,omitempty"`
Method string `json:"method,omitempty"`
}

View File

@@ -20,47 +20,47 @@ type SubService struct {
address string address string
showInfo bool showInfo bool
remarkModel string remarkModel string
datepicker string
inboundService service.InboundService inboundService service.InboundService
settingService service.SettingService settingService service.SettingService
} }
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) { func NewSubService(showInfo bool, remarkModel string) *SubService {
return &SubService{
showInfo: showInfo,
remarkModel: remarkModel,
}
}
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
s.address = host s.address = host
s.showInfo = showInfo
var result []string var result []string
var headers []string var header string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId) inbounds, err := s.getInboundsBySubId(subId)
if err != nil { if err != nil {
return nil, nil, err return nil, "", err
} }
s.remarkModel, err = s.settingService.GetRemarkModel()
s.datepicker, err = s.settingService.GetDatepicker()
if err != nil { if err != nil {
s.remarkModel = "-ieo" s.datepicker = "gregorian"
} }
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
if err != nil { if err != nil {
logger.Error("SubService - GetSub: Unable to get clients from inbound") logger.Error("SubService - GetClients: Unable to get clients from inbound")
} }
if clients == nil { if clients == nil {
continue continue
} }
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
fallbackMaster, err := s.getFallbackMaster(inbound.Listen) listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil { if err == nil {
inbound.Listen = fallbackMaster.Listen inbound.Listen = listen
inbound.Port = fallbackMaster.Port inbound.Port = port
var stream map[string]interface{} inbound.StreamSettings = streamSettings
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
inbound.StreamSettings = string(modifiedStream)
} }
} }
for _, client := range clients { for _, client := range clients {
@@ -71,6 +71,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
} }
} }
} }
// Prepare statistics
for index, clientTraffic := range clientTraffics { for index, clientTraffic := range clientTraffics {
if index == 0 { if index == 0 {
traffic.Up = clientTraffic.Up traffic.Up = clientTraffic.Up
@@ -92,11 +94,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
} }
} }
} }
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)) header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
updateInterval, _ := s.settingService.GetSubUpdates() return result, header, nil
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
return result, headers, nil
} }
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
@@ -125,7 +124,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
return xray.ClientTraffic{} return xray.ClientTraffic{}
} }
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) { func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
db := database.GetDB() db := database.GetDB()
var inbound *model.Inbound var inbound *model.Inbound
err := db.Model(model.Inbound{}). err := db.Model(model.Inbound{}).
@@ -133,9 +132,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest). Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
Find(&inbound).Error Find(&inbound).Error
if err != nil { if err != nil {
return nil, err return "", 0, "", err
} }
return inbound, nil
var stream map[string]interface{}
json.Unmarshal([]byte(streamSettings), &stream)
var masterStream map[string]interface{}
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
stream["externalProxy"] = masterStream["externalProxy"]
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
return inbound.Listen, inbound.Port, string(modifiedStream), nil
} }
func (s *SubService) getLink(inbound *model.Inbound, email string) string { func (s *SubService) getLink(inbound *model.Inbound, email string) string {
@@ -573,6 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string) params["sni"], _ = sniValue.(string)
} }
tlsSettings, _ := searchKey(tlsSetting, "settings") tlsSettings, _ := searchKey(tlsSetting, "settings")
if tlsSetting != nil { if tlsSetting != nil {
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {

View File

@@ -2,7 +2,6 @@ package random
import ( import (
"math/rand" "math/rand"
"time"
) )
var numSeq [10]rune var numSeq [10]rune
@@ -13,8 +12,6 @@ var numUpperSeq [36]rune
var allSeq [62]rune var allSeq [62]rune
func init() { func init() {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
numSeq[i] = rune('0' + i) numSeq[i] = rune('0' + i)
} }
@@ -41,3 +38,7 @@ func Seq(n int) string {
} }
return string(runes) return string(runes)
} }
func Num(n int) int {
return rand.Intn(n)
}

View File

@@ -538,7 +538,7 @@
var on = function(emitter, type, f) { var on = function(emitter, type, f) {
if (emitter.addEventListener) { if (emitter.addEventListener) {
emitter.addEventListener(type, f, false); emitter.addEventListener(type, f, { passive: true });
} else if (emitter.attachEvent) { } else if (emitter.attachEvent) {
emitter.attachEvent("on" + type, f); emitter.attachEvent("on" + type, f);
} else { } else {

View File

@@ -45,8 +45,8 @@ THE SOFTWARE.
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; } .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); } .dark .cm-s-xq.CodeMirror { background-color: #000000; border-color: #25272a; color: rgb(255 255 255 / 85%); }
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; } .dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 20%); border-color: #008771; transition: all .3s; }
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); } .dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); } .dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); } .dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }

View File

@@ -1,3 +1,18 @@
:root {
--dark-color-background: #21242a;
--dark-color-surface-100: #0c0e12;
--dark-color-surface-200: #222327;
--dark-color-surface-300: #32353b;
--dark-color-surface-400: rgba(255, 255, 255, 0.1);
--dark-color-surface-500: #3b404b;
--dark-color-surface-600: #505663;
--dark-color-text-primary: rgb(255 255 255 / 85%);
--dark-color-stroke: #202025;
--dark-color-btn-danger: #cd3838;
--dark-color-btn-danger-border: transparent;
--dark-color-btn-danger-hover: #e94b4b;
}
html, html,
body { body {
height: 100vh; height: 100vh;
@@ -502,13 +517,13 @@ style attribute {
.dark .ant-table, .dark .ant-table,
.dark .ant-collapse-content, .dark .ant-collapse-content,
.dark .ant-tabs { .dark .ant-tabs {
background-color: #151f31; background-color: var(--dark-color-surface-100);
color: #ffffffa6; color: var(--dark-color-text-primary);
} }
.dark .ant-card-hoverable:hover, .dark .ant-card-hoverable:hover,
.dark .ant-space-item > .ant-tabs:hover { .dark .ant-space-item > .ant-tabs:hover {
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%); box-shadow: 0 2px 8px transparent;
} }
.dark > .ant-layout, .dark > .ant-layout,
@@ -518,8 +533,8 @@ style attribute {
.dark .ant-table-expanded-row:hover, .dark .ant-table-expanded-row:hover,
.dark .ant-table-expanded-row .ant-table-tbody, .dark .ant-table-expanded-row .ant-table-tbody,
.dark .ant-calendar { .dark .ant-calendar {
background-color: #101828; background-color: var(--dark-color-background);
color: rgb(255 255 255 /65%); color: var(--dark-color-text-primary);
} }
.dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th { .dark .ant-table-expanded-row .ant-table-thead > tr:first-child > th {
@@ -528,7 +543,7 @@ style attribute {
.dark .ant-calendar, .dark .ant-calendar,
.dark .ant-card-bordered { .dark .ant-card-bordered {
border-color: #151f31; border-color: var(--dark-color-background);
} }
.dark .ant-table-bordered, .dark .ant-table-bordered,
@@ -540,7 +555,7 @@ style attribute {
.dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th, .dark .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
.dark .ant-table-bordered .ant-table-tbody > tr > td, .dark .ant-table-bordered .ant-table-tbody > tr > td,
.dark .ant-table-bordered .ant-table-thead > tr > th { .dark .ant-table-bordered .ant-table-thead > tr > th {
border-color: #2c3950; border-color: var(--dark-color-surface-400);
} }
.dark .ant-table-tbody > tr > td, .dark .ant-table-tbody > tr > td,
@@ -553,7 +568,7 @@ style attribute {
.dark .ant-popover-title, .dark .ant-popover-title,
.dark .ant-calendar-header, .dark .ant-calendar-header,
.dark .ant-calendar-input-wrap { .dark .ant-calendar-input-wrap {
border-bottom-color: #2c3950; border-bottom-color: var(--dark-color-surface-400);
} }
.dark .ant-modal-footer, .dark .ant-modal-footer,
@@ -561,7 +576,7 @@ style attribute {
.dark .ant-calendar-footer, .dark .ant-calendar-footer,
.dark .ant-divider-horizontal.ant-divider-with-text-center:before, .dark .ant-divider-horizontal.ant-divider-with-text-center:before,
.dark .ant-divider-horizontal.ant-divider-with-text-center:after { .dark .ant-divider-horizontal.ant-divider-with-text-center:after {
border-top-color: #2c3950; border-top-color: var(--dark-color-surface-300);
} }
.dark .ant-progress-text, .dark .ant-progress-text,
@@ -597,7 +612,7 @@ style attribute {
.dark .ant-calendar-year-panel-year, .dark .ant-calendar-year-panel-year,
.dark .ant-calendar-month-panel-month, .dark .ant-calendar-month-panel-month,
.dark .ant-calendar-decade-panel-decade { .dark .ant-calendar-decade-panel-decade {
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.85);
} }
.dark .ant-list-item-meta-description { .dark .ant-list-item-meta-description {
@@ -623,13 +638,12 @@ style attribute {
.dark .ant-select-dropdown li, .dark .ant-select-dropdown li,
.dark .ant-select-dropdown-menu-item, .dark .ant-select-dropdown-menu-item,
.dark .ant-divider:not(.ant-divider-with-text-center), .dark .ant-divider:not(.ant-divider-with-text-center),
.dark .ant-calendar-input,
.dark .client-table-header, .dark .client-table-header,
.dark .ant-select-selection--multiple .ant-select-selection__choice, .dark .ant-select-selection--multiple .ant-select-selection__choice,
.dark .ant-calendar-time-picker-inner { .dark .ant-calendar-time-picker-inner {
background-color: #222d42; background-color: var(--dark-color-surface-200);
border-color: #2c3950; border-color: var(--dark-color-surface-300);
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.85);
} }
.dark .ant-select-selection:hover, .dark .ant-select-selection:hover,
@@ -643,7 +657,7 @@ style attribute {
} }
.dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) { .dark .ant-btn:not(.ant-btn-primary):not(.ant-btn-danger) {
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.85);
background-color: rgb(10 117 87 / 30%); background-color: rgb(10 117 87 / 30%);
border: 1px solid #008771; border: 1px solid #008771;
} }
@@ -666,7 +680,7 @@ style attribute {
.dark .ant-btn-danger[disabled], .dark .ant-btn-danger[disabled],
.dark .ant-calendar-ok-btn-disabled { .dark .ant-calendar-ok-btn-disabled {
color: rgb(255 255 255 / 35%); color: rgb(255 255 255 / 35%);
background-color: #2c3950; background-color: var(--dark-color-surface-300);
border-color: #42516c; border-color: #42516c;
} }
@@ -675,7 +689,7 @@ style attribute {
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td, > td,
.dark .client-table-odd-row { .dark .client-table-odd-row {
background-color: #00877122; background-color: rgb(89 89 89 / 15%);
} }
.dark .ant-table-row-expand-icon { .dark .ant-table-row-expand-icon {
@@ -692,31 +706,31 @@ style attribute {
.dark .ant-switch:not(.ant-switch-checked), .dark .ant-switch:not(.ant-switch-checked),
.dark .ant-progress-line .ant-progress-inner { .dark .ant-progress-line .ant-progress-inner {
background-color: #2c3950; background-color: var(--dark-color-surface-500);
} }
.dark .ant-progress-circle-trail { .dark .ant-progress-circle-trail {
stroke: #2c3950 !important; stroke: var(--dark-color-stroke) !important;
} }
.ant-dropdown-menu-dark, .ant-dropdown-menu-dark,
.dark .ant-popover-inner { .dark .ant-popover-inner {
background-color: #222d42; background-color: var(--dark-color-surface-500);
} }
.dark > .ant-popover-content > .ant-popover-arrow { .dark > .ant-popover-content > .ant-popover-arrow {
border-color: #222d42; border-color: var(--dark-color-surface-500);
} }
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
.dark .ant-select-dropdown-menu-item-selected, .dark .ant-select-dropdown-menu-item-selected,
.dark .ant-select-dropdown-menu-item:hover, .dark .ant-select-dropdown-menu-item:hover,
.dark .ant-calendar-time-picker-select-option-selected { .dark .ant-calendar-time-picker-select-option-selected {
background-color: #313f5a; background-color: var(--dark-color-surface-600);
} }
.ant-menu-dark .ant-menu-item:hover { .ant-menu-dark .ant-menu-item:hover {
background-color: #2c3950; background-color: var(--dark-color-surface-300);
} }
.dark .ant-alert-message { .dark .ant-alert-message {
@@ -724,9 +738,9 @@ style attribute {
} }
.dark .ant-tag { .dark .ant-tag {
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.85);
background-color: #ffffff0a; background-color: rgba(255, 255, 255, 0.08);
border-color: #344461; border-color: rgba(255, 255, 255, 0.15);
} }
.dark .ant-tag-blue { .dark .ant-tag-blue {
@@ -737,38 +751,38 @@ style attribute {
.dark .ant-tag-red, .dark .ant-tag-red,
.dark .ant-alert-error { .dark .ant-alert-error {
background-color: #291515; background-color: #2a1215;
border-color: #5c2626; border-color: #58181c;
color: #e04141; color: #e84749;
} }
.dark .ant-tag-orange, .dark .ant-tag-orange,
.dark .ant-alert-warning { .dark .ant-alert-warning {
background-color: #312313; background-color: #2b1d11;
border-color: #593914; border-color: #593815;
color: #ffa031; color: #e89a3c;
} }
.dark .ant-tag-green { .dark .ant-tag-green {
background-color: #112421; background-color: #112421;
border-color: #144840; border-color: #195544;
color: #33bca5; color: #59cbac;
} }
.dark .ant-tag-purple { .dark .ant-tag-purple {
background-color: #2c1e32; background-color: #241121;
border-color: #49394e; border-color: #5a2969;
color: #cfb9cc; color: #d686ca;
} }
.dark .ant-modal-content, .dark .ant-modal-content,
.dark .ant-modal-header { .dark .ant-modal-header {
background-color: #181f2c; background-color: #101113;
} }
.dark .ant-calendar-next-month-btn-day .ant-calendar-date, .dark .ant-calendar-next-month-btn-day .ant-calendar-date,
.dark .ant-calendar-last-month-cell .ant-calendar-date { .dark .ant-calendar-last-month-cell .ant-calendar-date {
color: #2c3950; color: var(--dark-color-surface-300);
} }
.dark .ant-calendar-selected-day .ant-calendar-date { .dark .ant-calendar-selected-day .ant-calendar-date {
@@ -778,7 +792,7 @@ style attribute {
.dark .ant-calendar-date:hover, .dark .ant-calendar-date:hover,
.dark .ant-calendar-time-picker-select li:hover { .dark .ant-calendar-time-picker-select li:hover {
background-color: #313f5a; background-color: var(--dark-color-surface-300);
color: #fff; color: #fff;
} }
@@ -796,7 +810,7 @@ style attribute {
} }
.dark .ant-calendar-time-picker-select { .dark .ant-calendar-time-picker-select {
border-right-color: #2c3950; border-right-color: var(--dark-color-surface-300);
} }
.has-warning .ant-input, .has-warning .ant-input,
@@ -957,7 +971,7 @@ li.ant-select-dropdown-menu-item:empty:after {
} }
.dark .ant-calendar-year-panel-header { .dark .ant-calendar-year-panel-header {
border-bottom: 1px solid #222d42; border-bottom: 1px solid var(--dark-color-surface-200);
} }
.dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year, .dark .ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,
@@ -968,7 +982,7 @@ li.ant-select-dropdown-menu-item:empty:after {
.dark .ant-calendar-year-panel-year:hover, .dark .ant-calendar-year-panel-year:hover,
.dark .ant-calendar-month-panel-month:hover, .dark .ant-calendar-month-panel-month:hover,
.dark .ant-calendar-decade-panel-decade:hover { .dark .ant-calendar-decade-panel-decade:hover {
background-color: #222d42; background-color: var(--dark-color-surface-600);
} }
.dark .ant-calendar-header a:hover { .dark .ant-calendar-header a:hover {
@@ -976,13 +990,13 @@ li.ant-select-dropdown-menu-item:empty:after {
} }
.dark .ant-calendar-month-panel-header { .dark .ant-calendar-month-panel-header {
background-color: #101828; background-color: var(--dark-color-background);
border-bottom: 1px solid #222d42; border-bottom: 1px solid var(--dark-color-surface-200);
} }
.dark .ant-calendar-year-panel, .dark .ant-calendar-year-panel,
.dark .ant-calendar table { .dark .ant-calendar table {
background-color: #101828; background-color: var(--dark-color-background);
} }
.dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year, .dark .ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,
@@ -1028,8 +1042,8 @@ li.ant-select-dropdown-menu-item:empty:after {
} }
.dark .ant-calendar-decade-panel-header { .dark .ant-calendar-decade-panel-header {
border-bottom: 1px solid #222d42; border-bottom: 1px solid var(--dark-color-surface-200);
background-color: #101828; background-color: var(--dark-color-background);
} }
.dark .ant-checkbox-inner { .dark .ant-checkbox-inner {
@@ -1043,19 +1057,25 @@ li.ant-select-dropdown-menu-item:empty:after {
} }
.dark .ant-calendar-input { .dark .ant-calendar-input {
background-color: #101828; background-color: var(--dark-color-background);
color: var(--dark-color-text-primary);
} }
.dark .ant-calendar-input::placeholder { .dark .ant-calendar-input::placeholder {
color: rgba(255, 255, 255, 0.25); color: rgba(255, 255, 255, 0.25);
} }
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
:last-child
),
.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(
:last-child
),
.ant-input-group.ant-input-group-compact
> .ant-input:not(:first-child):not(:last-child),
.ant-input-number-handler,
.ant-input-number-handler-wrap { .ant-input-number-handler-wrap {
border-radius: 0; border-radius: 0;
}
.ant-input-number-handler {
border-radius: 0;
} }
.ant-input-number { .ant-input-number {
@@ -1089,15 +1109,15 @@ li.ant-select-dropdown-menu-item:empty:after {
> td, > td,
.ant-table-thead .ant-table-thead
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected) > tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td { > td,
.ant-calendar-time-picker-select li:hover {
background-color: rgb(232 244 242); background-color: rgb(232 244 242);
} }
.dark .ant-dropdown-menu-item:hover,
.dark .ant-dropdown-menu-submenu-title:hover, .dark .ant-dropdown-menu-submenu-title:hover,
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled), .dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) { .dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
background-color: #313f5a; background-color: var(--dark-color-surface-300);
} }
.ant-select-dropdown, .ant-select-dropdown,
@@ -1110,6 +1130,8 @@ li.ant-select-dropdown-menu-item:empty:after {
} }
.qr-bg { .qr-bg {
width: 100%;
height: 100%;
background-color: #fff; background-color: #fff;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -1117,3 +1139,39 @@ li.ant-select-dropdown-menu-item:empty:after {
padding: 0.5rem; padding: 0.5rem;
border-radius: 1rem; border-radius: 1rem;
} }
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
border-radius: 0rem 1rem 1rem 0rem;
}
.ant-tag {
margin-right: 6px;
}
b, strong {
font-weight: 500;
}
.ant-collapse>.ant-collapse-item>.ant-collapse-header {
padding: 10px 16px 10px 40px;
}
.dark .ant-message-notice-content {
background-color: #000000;
border: 1px solid #303134;
color: rgba(255, 255, 255, 0.85);
}
.ant-btn-danger {
background-color: var(--dark-color-btn-danger);
border-color: var(--dark-color-btn-danger-border);
}
.ant-btn-danger:focus, .ant-btn-danger:hover {
background-color: var(--dark-color-btn-danger-hover);
border-color: var(--dark-color-btn-danger-hover);
}
.dark .ant-alert-close-icon .anticon-close:hover {
color: rgb(255 255 255);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -29,6 +29,11 @@ const supportLangs = [
value: 'es-ES', value: 'es-ES',
icon: '🇪🇸', icon: '🇪🇸',
}, },
{
name: 'Indonesian',
value: 'id-ID',
icon: '🇮🇩',
},
]; ];
function getLang() { function getLang() {

View File

@@ -56,6 +56,10 @@ class DBInbound {
return this.protocol === Protocols.HTTP; return this.protocol === Protocols.HTTP;
} }
get isWireguard() {
return this.protocol === Protocols.WIREGUARD;
}
get address() { get address() {
let address = location.hostname; let address = location.hostname;
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") { if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {

View File

@@ -8,6 +8,7 @@ const Protocols = {
Shadowsocks: "shadowsocks", Shadowsocks: "shadowsocks",
Socks: "socks", Socks: "socks",
HTTP: "http", HTTP: "http",
Wireguard: "wireguard"
}; };
const SSMethods = { const SSMethods = {
@@ -46,18 +47,27 @@ const ALPN_OPTION = {
HTTP1: "http/1.1", HTTP1: "http/1.1",
}; };
const outboundDomainStrategies = [ const OutboundDomainStrategies = [
"AsIs", "AsIs",
"UseIP", "UseIP",
"UseIPv4", "UseIPv4",
"UseIPv6" "UseIPv6"
] ];
const WireguardDomainStrategy = [
"ForceIP",
"ForceIPv4",
"ForceIPv4v6",
"ForceIPv6",
"ForceIPv6v4"
];
Object.freeze(Protocols); Object.freeze(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(ALPN_OPTION); Object.freeze(ALPN_OPTION);
Object.freeze(outboundDomainStrategies); Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
class CommonClass { class CommonClass {
@@ -408,7 +418,7 @@ class Outbound extends CommonClass {
} }
canEnableTls() { canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false; if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network); return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
} }
@@ -625,6 +635,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings(); case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
case Protocols.Socks: return new Outbound.SocksSettings(); case Protocols.Socks: return new Outbound.SocksSettings();
case Protocols.HTTP: return new Outbound.HttpSettings(); case Protocols.HTTP: return new Outbound.HttpSettings();
case Protocols.Wireguard: return new Outbound.WireguardSettings();
default: return null; default: return null;
} }
} }
@@ -640,6 +651,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json); case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json); case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json); case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
default: return null; default: return null;
} }
} }
@@ -838,23 +850,24 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
}; };
} }
}; };
Outbound.SocksSettings = class extends CommonClass { Outbound.SocksSettings = class extends CommonClass {
constructor(address, port, user, password) { constructor(address, port, user, pass) {
super(); super();
this.address = address; this.address = address;
this.port = port; this.port = port;
this.user = user; this.user = user;
this.password = password; this.pass = pass;
} }
static fromJson(json={}) { static fromJson(json={}) {
servers = json.servers; let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.SocksSettings( return new Outbound.SocksSettings(
servers[0].address, servers[0].address,
servers[0].port, servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
); );
} }
@@ -863,28 +876,28 @@ Outbound.SocksSettings = class extends CommonClass {
servers: [{ servers: [{
address: this.address, address: this.address,
port: this.port, port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}], users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
}], }],
}; };
} }
}; };
Outbound.HttpSettings = class extends CommonClass { Outbound.HttpSettings = class extends CommonClass {
constructor(address, port, user, password) { constructor(address, port, user, pass) {
super(); super();
this.address = address; this.address = address;
this.port = port; this.port = port;
this.user = user; this.user = user;
this.password = password; this.pass = pass;
} }
static fromJson(json={}) { static fromJson(json={}) {
servers = json.servers; let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}]; if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.HttpSettings( return new Outbound.HttpSettings(
servers[0].address, servers[0].address,
servers[0].port, servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password, ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
); );
} }
@@ -893,8 +906,91 @@ Outbound.HttpSettings = class extends CommonClass {
servers: [{ servers: [{
address: this.address, address: this.address,
port: this.port, port: this.port,
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}], users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
}], }],
}; };
} }
}; };
Outbound.WireguardSettings = class extends CommonClass {
constructor(
mtu=1420, secretKey='',
address=[''], workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.address = address instanceof Array ? address.join(',') : address;
this.workers = workers;
this.domainStrategy = domainStrategy;
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Outbound.WireguardSettings.Peer());
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Outbound.WireguardSettings(
json.mtu,
json.secretKey,
json.address,
json.workers,
json.domainStrategy,
json.reserved,
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
address: this.address ? this.address.split(",") : [],
workers: this.workers?? undefined,
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Outbound.WireguardSettings.Peer = class extends CommonClass {
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
this.allowedIPs = allowedIPs;
this.endpoint = endpoint;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Outbound.WireguardSettings.Peer(
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.endpoint,
json.keepAlive
);
}
toJson() {
return {
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs ? this.allowedIPs : undefined,
endpoint: this.endpoint,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -12,8 +12,10 @@ class AllSetting {
this.expireDiff = ""; this.expireDiff = "";
this.trafficDiff = ""; this.trafficDiff = "";
this.remarkModel = "-ieo"; this.remarkModel = "-ieo";
this.datepicker = "gregorian";
this.tgBotEnable = false; this.tgBotEnable = false;
this.tgBotToken = ""; this.tgBotToken = "";
this.tgBotProxy = "";
this.tgBotChatId = ""; this.tgBotChatId = "";
this.tgRunTime = "@daily"; this.tgRunTime = "@daily";
this.tgBotBackup = false; this.tgBotBackup = false;
@@ -26,6 +28,7 @@ class AllSetting {
this.subListen = ""; this.subListen = "";
this.subPort = "2096"; this.subPort = "2096";
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.subCertFile = ""; this.subCertFile = "";
this.subKeyFile = ""; this.subKeyFile = "";
@@ -33,6 +36,8 @@ class AllSetting {
this.subEncrypt = true; this.subEncrypt = true;
this.subShowInfo = false; this.subShowInfo = false;
this.subURI = ''; this.subURI = '';
this.subJsonURI = '';
this.subJsonFragment = '';
this.timeLocation = "Asia/Tehran"; this.timeLocation = "Asia/Tehran";

View File

@@ -6,6 +6,7 @@ const Protocols = {
DOKODEMO: 'dokodemo-door', DOKODEMO: 'dokodemo-door',
SOCKS: 'socks', SOCKS: 'socks',
HTTP: 'http', HTTP: 'http',
WIREGUARD: 'wireguard',
}; };
const SSMethods = { const SSMethods = {
@@ -235,6 +236,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
toJson() { toJson() {
return { return {
version: this.version,
method: this.method, method: this.method,
path: ObjectUtil.clone(this.path), path: ObjectUtil.clone(this.path),
headers: XrayCommonClass.toV2Headers(this.headers), headers: XrayCommonClass.toV2Headers(this.headers),
@@ -764,16 +766,18 @@ class RealityStreamSettings extends XrayCommonClass {
} }
RealityStreamSettings.Settings = class extends XrayCommonClass { RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, spiderX= '/') { constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
super(); super();
this.publicKey = publicKey; this.publicKey = publicKey;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this.serverName = serverName;
this.spiderX = spiderX; this.spiderX = spiderX;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new RealityStreamSettings.Settings( return new RealityStreamSettings.Settings(
json.publicKey, json.publicKey,
json.fingerprint, json.fingerprint,
json.serverName,
json.spiderX, json.spiderX,
); );
} }
@@ -781,6 +785,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
return { return {
publicKey: this.publicKey, publicKey: this.publicKey,
fingerprint: this.fingerprint, fingerprint: this.fingerprint,
serverName: this.serverName,
spiderX: this.spiderX, spiderX: this.spiderX,
}; };
} }
@@ -794,6 +799,7 @@ class SockoptStreamSettings extends XrayCommonClass {
this.mark = mark; this.mark = mark;
this.tproxy = tproxy; this.tproxy = tproxy;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined; if (Object.keys(json).length === 0) return undefined;
return new SockoptStreamSettings( return new SockoptStreamSettings(
@@ -995,18 +1001,6 @@ class Inbound extends XrayCommonClass {
} }
} }
get tls() {
return this.stream.security === 'tls';
}
set tls(isTls) {
if (isTls) {
this.stream.security = 'tls';
} else {
this.stream.security = 'none';
}
}
get xtls() { get xtls() {
return this.stream.security === 'xtls'; return this.stream.security === 'xtls';
} }
@@ -1019,18 +1013,6 @@ class Inbound extends XrayCommonClass {
} }
} }
get reality() {
return this.stream.security === 'reality';
}
set reality(isReality) {
if (isReality) {
this.stream.security = 'reality';
} else {
this.stream.security = 'none';
}
}
get network() { get network() {
return this.stream.network; return this.stream.network;
} }
@@ -1142,11 +1124,6 @@ class Inbound extends XrayCommonClass {
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network); return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
} }
canEnableReality() {
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return ["tcp", "http", "grpc"].includes(this.network);
}
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
canEnableTlsFlow() { canEnableTlsFlow() {
if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) { if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
@@ -1155,6 +1132,11 @@ class Inbound extends XrayCommonClass {
return false; return false;
} }
canEnableReality() {
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return ["tcp", "http", "grpc"].includes(this.network);
}
canEnableXtls() { canEnableXtls() {
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false; if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return this.network === "tcp"; return this.network === "tcp";
@@ -1164,10 +1146,6 @@ class Inbound extends XrayCommonClass {
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol); return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
} }
canSniffing() {
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
}
reset() { reset() {
this.port = RandomUtil.randomIntRange(10000, 60000); this.port = RandomUtil.randomIntRange(10000, 60000);
this.listen = ''; this.listen = '';
@@ -1552,6 +1530,28 @@ class Inbound extends XrayCommonClass {
return url.toString(); return url.toString();
} }
getWireguardLink(address, port, remark, peerId) {
let txt = `[Interface]\n`
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
txt += `DNS = 1.1.1.1, 1.0.0.1\n`
if (this.settings.mtu) {
txt += `MTU = ${this.settings.mtu}\n`
}
txt += `\n# ${remark}\n`
txt += `[Peer]\n`
txt += `PublicKey = ${this.settings.pubKey}\n`
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
txt += `Endpoint = ${address}:${port}`
if (this.settings.peers[peerId].psk) {
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
}
if (this.settings.peers[peerId].keepAlive) {
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
}
return txt;
}
genLink(address='', port=this.port, forceTls='same', remark='', client) { genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VMESS: case Protocols.VMESS:
@@ -1575,7 +1575,7 @@ class Inbound extends XrayCommonClass {
const orderChars = remarkModel.slice(1); const orderChars = remarkModel.slice(1);
let orders = { let orders = {
'i': remark, 'i': remark,
'e': client ? client.email : '', 'e': email,
'o': '', 'o': '',
}; };
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){ if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
@@ -1598,6 +1598,7 @@ class Inbound extends XrayCommonClass {
} }
genInboundLinks(remark = '', remarkModel = '-ieo') { genInboundLinks(remark = '', remarkModel = '-ieo') {
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
if(this.clients){ if(this.clients){
let links = []; let links = [];
this.clients.forEach((client) => { this.clients.forEach((client) => {
@@ -1607,7 +1608,14 @@ class Inbound extends XrayCommonClass {
}); });
return links.join('\r\n'); return links.join('\r\n');
} else { } else {
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, remark); if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
if(this.protocol == Protocols.WIREGUARD) {
let links = [];
this.settings.peers.forEach((p,index) => {
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
});
return links.join('\r\n');
}
return ''; return '';
} }
} }
@@ -1658,6 +1666,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
case Protocols.HTTP: return new Inbound.HttpSettings(protocol); case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
default: return null; default: return null;
} }
} }
@@ -1671,6 +1680,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
default: return null; default: return null;
} }
} }
@@ -2273,3 +2283,81 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
return new Inbound.HttpSettings.HttpAccount(json.user, json.pass); return new Inbound.HttpSettings.HttpAccount(json.user, json.pass);
} }
}; };
Inbound.WireguardSettings = class extends XrayCommonClass {
constructor(protocol, mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, peers=[new Inbound.WireguardSettings.Peer()], kernelMode=false) {
super(protocol);
this.mtu = mtu;
this.secretKey = secretKey;
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
this.peers = peers;
this.kernelMode = kernelMode;
}
addPeer() {
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
}
delPeer(index) {
this.peers.splice(index, 1);
}
static fromJson(json={}){
return new Inbound.WireguardSettings(
Protocols.WIREGUARD,
json.mtu,
json.secretKey,
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
json.kernelMode,
);
}
toJson() {
return {
mtu: this.mtu?? undefined,
secretKey: this.secretKey,
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
kernelMode: this.kernelMode,
};
}
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
})
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
json.privateKey,
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.keepAlive
);
}
toJson() {
this.allowedIPs.forEach((a,index) => {
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
});
return {
privateKey: this.privateKey,
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs,
keepAlive: this.keepAlive?? undefined,
};
}
};

View File

@@ -295,3 +295,190 @@ class ObjectUtil {
return true; return true;
} }
} }
class Wireguard {
static gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
static pack(o, n) {
var b, m = this.gf(), t = this.gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
this.carry(t);
this.carry(t);
this.carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
this.cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
static cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
static subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
static multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
this.carry(o);
this.carry(o);
}
static invert(o, i) {
var c = this.gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
this.multmod(c, c, c);
if (a !== 2 && a !== 4)
this.multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
static clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
static generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = this.gf([1]),
b = this.gf([9]),
c = this.gf(),
d = this.gf([1]),
e = this.gf(),
f = this.gf(),
_121665 = this.gf([0xdb41, 1]),
_9 = this.gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
this.clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
this.cswap(a, b, r);
this.cswap(c, d, r);
this.add(e, a, c);
this.subtract(a, a, c);
this.add(c, b, d);
this.subtract(b, b, d);
this.multmod(d, e, e);
this.multmod(f, a, a);
this.multmod(a, c, a);
this.multmod(c, b, e);
this.add(e, a, c);
this.subtract(a, a, c);
this.multmod(b, a, a);
this.subtract(c, d, f);
this.multmod(a, c, _121665);
this.add(a, a, d);
this.multmod(c, c, a);
this.multmod(a, d, f);
this.multmod(d, b, _9);
this.multmod(b, e, e);
this.cswap(a, b, r);
this.cswap(c, d, r);
}
this.invert(c, c);
this.multmod(a, a, c);
this.pack(z, a);
return z;
}
static generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
static generatePrivateKey() {
var privateKey = this.generatePresharedKey();
this.clamp(privateKey);
return privateKey;
}
static encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
static keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
static keyFromBase64(encoded) {
const binaryStr = atob(encoded);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}
static generateKeypair(secretKey='') {
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
var publicKey = this.generatePublicKey(privateKey);
return {
publicKey: this.keyToBase64(publicKey),
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
};
}
}

1252
web/assets/moment/moment-jalali.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,455 @@
jdp-overlay {
height: 0;
width: 0;
}
jdp-container {
-moz-animation: 0.3s cubic-bezier(0.23, 1, 0.32, 1) jdpOpenAnimation;
-webkit-animation: 0.3s cubic-bezier(0.23, 1, 0.32, 1) jdpOpenAnimation;
animation: 0.3s cubic-bezier(0.23, 1, 0.32, 1) jdpOpenAnimation;
background: #fff;
border-radius: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,.15);
direction: rtl;
display: none;
width: 280px;
overflow: hidden;
padding: 0.5rem 0;
position: absolute;
-ms-touch-action: manipulation;
touch-action: manipulation;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
transform-origin: bottom;
}
jdp-container,
jdp-container *,
jdp-container :after,
jdp-container :before {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
jdp-container .jdp-icon-minus,
jdp-container .jdp-icon-plus {
outline: 1px solid rgb(232 244 242);
outline-offset: -1px;
border-radius: 6px;
cursor: pointer;
display: flex;
flex: none;
overflow: hidden;
text-align: center;
text-decoration: none;
vertical-align: middle;
transition: all 0.2s;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
}
jdp-container .jdp-icon-minus:hover,
jdp-container .jdp-icon-plus:hover {
background-color: rgb(232 244 242);
}
jdp-container .jdp-icon-minus svg,
jdp-container .jdp-icon-plus svg {
height: 1.5rem;
padding: 0.25rem;
vertical-align: middle;
width: 1.5rem;
}
jdp-container .jdp-icon-minus.not-in-range,
jdp-container .jdp-icon-plus.not-in-range {
cursor: not-allowed;
}
jdp-container .jdp-icon-minus.not-in-range svg,
jdp-container .jdp-icon-plus.not-in-range svg {
opacity: 0.3;
}
jdp-container .jdp-months,
jdp-container .jdp-years {
fill: rgba(0, 0, 0, 0.9);
color: rgba(0, 0, 0, 0.9);
display: -webkit-inline-box;
display: -webkit-flex;
display: -ms-inline-flexbox;
display: inline-flex;
font-size: 120%;
margin: 0 2.5%;
}
jdp-container .jdp-months {
width: 40%;
margin-right: 1rem;
}
jdp-container .jdp-years {
width: 40%;
margin: 0.5rem 1.2rem 0.8rem 0;
}
jdp-container .jdp-month,
jdp-container .jdp-month input,
jdp-container .jdp-month select,
jdp-container .jdp-time,
jdp-container .jdp-time input,
jdp-container .jdp-time select,
jdp-container .jdp-year,
jdp-container .jdp-year input,
jdp-container .jdp-year select {
background: #fff;
border: none;
border-radius: 0;
color: inherit;
display: inline-block;
font-family: inherit;
font-size: inherit;
font-weight: 300;
height: auto;
line-height: inherit;
margin: 0;
outline: 0;
padding: 0;
text-align: center;
vertical-align: initial;
width: 100%;
font-feature-settings: "ss01";
}
jdp-container .jdp-month input:active,
jdp-container .jdp-month input:focus,
jdp-container .jdp-month select:active,
jdp-container .jdp-month select:focus,
jdp-container .jdp-month:active,
jdp-container .jdp-month:focus,
jdp-container .jdp-time input:active,
jdp-container .jdp-time input:focus,
jdp-container .jdp-time select:active,
jdp-container .jdp-time select:focus,
jdp-container .jdp-time:active,
jdp-container .jdp-time:focus,
jdp-container .jdp-year input:active,
jdp-container .jdp-year input:focus,
jdp-container .jdp-year select:active,
jdp-container .jdp-year select:focus,
jdp-container .jdp-year:active,
jdp-container .jdp-year:focus {
outline: 0;
}
jdp-container .jdp-month input option,
jdp-container .jdp-month option,
jdp-container .jdp-month select option,
jdp-container .jdp-time input option,
jdp-container .jdp-time option,
jdp-container .jdp-time select option,
jdp-container .jdp-year input option,
jdp-container .jdp-year option,
jdp-container .jdp-year select option {
font-size: 95%;
min-height: 1.3rem;
outline: 0;
padding: 0;
}
jdp-container .jdp-month input,
jdp-container .jdp-time input,
jdp-container .jdp-year input {
-webkit-appearance: none;
-moz-appearance: textfield;
cursor: text;
}
jdp-container .jdp-month input::-webkit-inner-spin-button,
jdp-container .jdp-month input::-webkit-outer-spin-button,
jdp-container .jdp-time input::-webkit-inner-spin-button,
jdp-container .jdp-time input::-webkit-outer-spin-button,
jdp-container .jdp-year input::-webkit-inner-spin-button,
jdp-container .jdp-year input::-webkit-outer-spin-button {
-webkit-appearance: none;
}
jdp-container .jdp-month select,
jdp-container .jdp-time select,
jdp-container .jdp-year select {
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
appearance: none;
position: relative;
}
jdp-container .jdp-days {
-ms-flex-pack: justify;
display: inline-block;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
justify-content: space-around;
outline: 0;
padding: 8px 12px;
text-align: left;
width: 100%;
border-top: 1px solid #e8e8e8;
}
jdp-container .jdp-day,
jdp-container .jdp-day-name {
background: 0 0;
border: 1px solid transparent;
color: rgba(0,0,0,.65);
display: block;
font-weight: 400;
height: 24px;
justify-content: center;
line-height: 22px;
margin: 2px 6px;
position: relative;
text-align: center;
width: 24px;
font-feature-settings: "ss01";
}
jdp-container .jdp-day-name.today,
jdp-container .jdp-day.today {
border-color: #008771;
color: #008771;
font-weight: 700;
}
.dark jdp-container .jdp-day-name.selected,
.dark jdp-container .jdp-day.selected,
jdp-container .jdp-day-name.selected,
jdp-container .jdp-day.selected {
background-color: #008771 !important;
color: #fff !important;
opacity: 1 !important;
}
.dark jdp-container .jdp-day-name.holly-day,
.dark jdp-container .jdp-day-name.last-week,
.dark jdp-container .jdp-day.holly-day,
.dark jdp-container .jdp-day.last-week,
jdp-container .jdp-day-name.holly-day,
jdp-container .jdp-day-name.last-week,
jdp-container .jdp-day.holly-day,
jdp-container .jdp-day.last-week {
color: #f44336;
}
.dark jdp-container .jdp-day.not-in-month,
jdp-container .jdp-day.not-in-month {
opacity: 0.4;
}
jdp-container .jdp-day.disabled-day {
cursor: not-allowed;
opacity: 0.15;
}
jdp-container .jdp-day:not(.disabled-day) {
border-radius: 6px;
cursor: pointer;
transition: 0.1s linear;
}
jdp-container .jdp-day:not(.disabled-day):hover {
background: rgb(232 244 242);
}
jdp-container .jdp-day-name {
background-color: rgb(0 0 0 / 0%);
border-radius: 6px;
cursor: default;
}
jdp-container .jdp-footer {
-ms-flex-pack: justify;
display: inline-block;
display: -ms-flexbox;
display: flex;
flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
justify-content: space-between;
outline: 0;
padding: 6px 12px 0;
width: 100%;
border-top: 1px solid #e8e8e8;
}
jdp-container .jdp-btn-close,
jdp-container .jdp-btn-empty,
jdp-container .jdp-btn-today {
background: #00877000;
border-radius: 5px;
color: #008771;
cursor: pointer;
display: inline-block;
font-size: 90%;
font-weight: 400;
padding: 0.3em 0.6em;
text-align: center;
}
jdp-container .jdp-btn-close.disabled-btn,
jdp-container .jdp-btn-empty.disabled-btn,
jdp-container .jdp-btn-today.disabled-btn {
cursor: not-allowed;
opacity: 0.2;
}
jdp-container .jdp-time-container {
display: flex;
padding: 6px 12px 12px 12px;
}
jdp-container .jdp-time-container .jdp-time {
flex: auto;
margin: 0 0.5rem;
position: relative;
}
jdp-container .jdp-time-container .jdp-time select {
border: 1px solid rgb(232 244 242);
border-radius: 6px;
appearance: none;
transition: all 0.2s;
}
jdp-container .jdp-time-container .jdp-time select:hover {
background-color: rgb(232 244 242);
}
jdp-container .jdp-time-container .jdp-time:after {
content: ":";
font-size: 1.5rem;
height: 100%;
position: absolute;
right: -0.7rem;
transform: translateY(-50%);
}
jdp-container .jdp-time-container .jdp-time:first-child:after {
display: none;
}
jdp-container .jdp-time-container.jdp-only-time .jdp-time select {
font-size: 1.5rem;
padding: 0.8rem 1rem 0.8rem 7px;
}
jdp-container .jdp-time-container.jdp-only-time .jdp-time:after {
font-size: 2.3rem;
position: absolute;
right: -0.8rem;
}
@-webkit-keyframes jdpOpenAnimation {
0% {
transform: scaleY(.8);
transform-origin: 0% 0%;
opacity: 0
}
to {
transform: scaleY(1);
transform-origin: 0% 0%;
opacity: 1
}
}
@keyframes jdpOpenAnimation {
0% {
transform: scaleY(.8);
transform-origin: 0% 0%;
opacity: 0
}
to {
transform: scaleY(1);
transform-origin: 0% 0%;
opacity: 1
}
}
@-webkit-keyframes jdpOpenAnimationMobile {
0% {
bottom: -10%;
opacity: 0;
}
to {
bottom: 0;
opacity: 1;
}
}
@keyframes jdpOpenAnimationMobile {
0% {
margin-bottom: -20%;
opacity: 0;
}
to {
margin-bottom: 0;
opacity: 1;
}
}
.dark jdp-container .jdp-days {
border-color: #32353b;
}
.dark jdp-overlay {
background-color: #181f2c;
}
.dark jdp-container {
background: #000000;
border-color: #2c3950;
box-shadow: 0 2px 8px rgba(0,0,0,.15);
color: #fff;
}
.dark jdp-container .jdp-icon-minus,
.dark jdp-container .jdp-icon-plus {
outline-color: #32353b;
}
.dark jdp-container .jdp-icon-minus:hover,
.dark jdp-container .jdp-icon-plus:hover {
background-color: #32353b;
}
.dark jdp-container .jdp-months,
.dark jdp-container .jdp-years {
fill: rgba(255, 255, 255, 0.9);
color: rgba(255, 255, 255, 0.9);
}
.dark jdp-container .jdp-month,
.dark jdp-container .jdp-month input,
.dark jdp-container .jdp-month select,
.dark jdp-container .jdp-time,
.dark jdp-container .jdp-time input,
.dark jdp-container .jdp-time select,
.dark jdp-container .jdp-year,
.dark jdp-container .jdp-year input,
.dark jdp-container .jdp-year select {
background: #000000;
color: rgba(255, 255, 255, 0.85);
}
.dark jdp-container .jdp-day,
.dark jdp-container .jdp-day-name {
border: 1px solid transparent;
color: rgba(255, 255, 255, 0.85);
}
.dark jdp-container .jdp-day-name.today,
.dark jdp-container .jdp-day.today {
border-color: #008771;
}
.dark jdp-container .jdp-day.disabled-day {
opacity: 0.15;
}
.dark jdp-container .jdp-day:not(.disabled-day):hover {
background-color: #32353b;
color: #fff;
}
.dark jdp-container .jdp-footer {
border-color: #32353b;
}
.dark jdp-container .jdp-btn-close,
.dark jdp-container .jdp-btn-empty,
.dark jdp-container .jdp-btn-today {
color: rgb(255 255 255 / 65%);
}
.dark jdp-container .jdp-btn-close:hover,
.dark jdp-container .jdp-btn-empty:hover,
.dark jdp-container .jdp-btn-today:hover {
color: rgb(255, 255, 255);
}
.dark jdp-container .jdp-btn-close.disabled-btn,
.dark jdp-container .jdp-btn-empty.disabled-btn,
.dark jdp-container .jdp-btn-today.disabled-btn {
opacity: 0.2;
}
.dark jdp-container .jdp-time-container .jdp-time select:hover {
background-color: #32353b;
color: #fff;
}
.dark jdp-container .jdp-time-container .jdp-time select {
border: 1px solid #32353b;
}

File diff suppressed because one or more lines are too long

View File

@@ -85,7 +85,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
} }
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.UserId = user.Id inbound.UserId = user.Id
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
inbound, needRestart, err = a.inboundService.AddInbound(inbound) inbound, needRestart, err = a.inboundService.AddInbound(inbound)
@@ -278,7 +282,11 @@ func (a *InboundController) importInbound(c *gin.Context) {
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
inbound.Id = 0 inbound.Id = 0
inbound.UserId = user.Id inbound.UserId = user.Id
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
for index := range inbound.ClientStats { for index := range inbound.ClientStats {
inbound.ClientStats[index].Id = 0 inbound.ClientStats[index].Id = 0

View File

@@ -10,6 +10,7 @@ type XraySettingController struct {
XraySettingService service.XraySettingService XraySettingService service.XraySettingService
SettingService service.SettingService SettingService service.SettingService
InboundService service.InboundService InboundService service.InboundService
OutboundService service.OutboundService
XrayService service.XrayService XrayService service.XrayService
} }
@@ -26,6 +27,9 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g.POST("/update", a.updateSetting) g.POST("/update", a.updateSetting)
g.GET("/getXrayResult", a.getXrayResult) g.GET("/getXrayResult", a.getXrayResult)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig) g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
} }
func (a *XraySettingController) getXraySetting(c *gin.Context) { func (a *XraySettingController) getXraySetting(c *gin.Context) {
@@ -61,3 +65,43 @@ func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
func (a *XraySettingController) getXrayResult(c *gin.Context) { func (a *XraySettingController) getXrayResult(c *gin.Context) {
jsonObj(c, a.XrayService.GetXrayResult(), nil) jsonObj(c, a.XrayService.GetXrayResult(), nil)
} }
func (a *XraySettingController) warp(c *gin.Context) {
action := c.Param("action")
var resp string
var err error
switch action {
case "data":
resp, err = a.XraySettingService.GetWarp()
case "config":
resp, err = a.XraySettingService.GetWarpConfig()
case "reg":
skey := c.PostForm("privateKey")
pkey := c.PostForm("publicKey")
resp, err = a.XraySettingService.RegWarp(skey, pkey)
case "license":
license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license)
}
jsonObj(c, resp, err)
}
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
if err != nil {
jsonMsg(c, "Error getting traffics", err)
return
}
jsonObj(c, outboundsTraffic, nil)
}
func (a *XraySettingController) resetOutboundsTraffic(c *gin.Context) {
tag := c.PostForm("tag")
err := a.OutboundService.ResetOutboundTraffic(tag)
if err != nil {
jsonMsg(c, "Error in reset outbound traffics", err)
return
}
jsonObj(c, "", nil)
}

View File

@@ -28,6 +28,7 @@ type AllSetting struct {
RemarkModel string `json:"remarkModel" form:"remarkModel"` RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
@@ -47,6 +48,10 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"` SubURI string `json:"subURI" form:"subURI"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
Datepicker string `json:"datepicker" form:"datepicker"`
} }
func (s *AllSetting) CheckValid() error { func (s *AllSetting) CheckValid() error {
@@ -103,6 +108,13 @@ func (s *AllSetting) CheckValid() error {
s.SubPath += "/" s.SubPath += "/"
} }
if !strings.HasPrefix(s.SubJsonPath, "/") {
s.SubJsonPath = "/" + s.SubJsonPath
}
if !strings.HasSuffix(s.SubJsonPath, "/") {
s.SubJsonPath += "/"
}
_, err := time.LoadLocation(s.TimeLocation) _, err := time.LoadLocation(s.TimeLocation)
if err != nil { if err != nil {
return common.NewError("time location not exist:", s.TimeLocation) return common.NewError("time location not exist:", s.TimeLocation)

View File

@@ -7,8 +7,6 @@
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css"> <link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css"> <link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}"> <link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
<style> <style>
[v-cloak] { [v-cloak] {
display: none; display: none;
@@ -30,4 +28,5 @@
</style> </style>
<title>{{ .host }}-{{ i18n .title}}</title> <title>{{ .host }}-{{ i18n .title}}</title>
</head> </head>
<div id="message"></div>
{{end}} {{end}}

View File

@@ -8,13 +8,23 @@
{{ i18n "pages.inbounds.clickOnQRcode" }} {{ i18n "pages.inbounds.clickOnQRcode" }}
</a-tag> </a-tag>
<template v-if="app.subSettings.enable && qrModal.subId"> <template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>Subscription</a-divider> <a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div> <canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
id="qrCode-sub"
class="qr-bg">
</canvas>
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
id="qrCode-subJson"
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
</canvas>
</template> </template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider> <a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<template v-for="(row, index) in qrModal.qrcodes"> <template v-for="(row, index) in qrModal.qrcodes">
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag> <a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div> <canvas @click="copyToClipboard('qrCode-'+index, row.link)"
:id="'qrCode-'+index"
class="qr-bg"></canvas>
</template> </template>
</a-modal> </a-modal>
@@ -35,12 +45,21 @@
this.client = client; this.client = client;
this.subId = ''; this.subId = '';
this.qrcodes = []; this.qrcodes = [];
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => { if (this.inbound.protocol == Protocols.WIREGUARD){
this.qrcodes.push({ this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
remark: l.remark, this.qrcodes.push({
link: l.link remark: "Peer " + (index+1),
link: l
});
}); });
}); } else {
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true; this.visible = true;
}, },
close: function () { close: function () {
@@ -73,12 +92,16 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}, },
updated() { updated() {
if (qrModal.client && qrModal.client.subId) { if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId; qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId)); this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
} }
qrModal.qrcodes.forEach((element, index) => { qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link); this.setQrCode("qrCode-" + index, element.link);

View File

@@ -1,15 +1,16 @@
{{define "textModal"}} {{define "textModal"}}
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title" <a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}' :closable="true"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme">
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}"> <template slot="footer">
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;" <a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName"> :download="txtModal.fileName">[[ txtModal.fileName ]]
{{ i18n "download" }} [[ txtModal.fileName ]] </a-button>
</a-button> <a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
<a-input type="textarea" v-model="txtModal.content" </template>
:autosize="{ minRows: 10, maxRows: 20}"></a-input> <a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
</a-modal> </a-modal>
<script> <script>
@@ -28,7 +29,7 @@
this.visible = true; this.visible = true;
textModalApp.$nextTick(() => { textModalApp.$nextTick(() => {
if (this.clipboard === null) { if (this.clipboard === null) {
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', { this.clipboard = new ClipboardJS('#copy-btn', {
text: () => this.content, text: () => this.content,
}); });
this.clipboard.on('success', () => { this.clipboard.on('success', () => {

View File

@@ -2,9 +2,14 @@
<html lang="en"> <html lang="en">
{{template "head" .}} {{template "head" .}}
<style> <style>
html * {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1 { h1 {
text-align: center; text-align: center;
margin: 20px 0 50px 0; /* margin: 20px 0 50px 0;*/
height: 110px;
} }
.ant-btn, .ant-btn,
.ant-input { .ant-input {
@@ -31,7 +36,9 @@
} }
.title { .title {
font-size: 32px; font-size: 32px;
font-weight: bold; }
.title b {
font-weight: bold !important;
} }
#app { #app {
overflow: hidden; overflow: hidden;
@@ -64,10 +71,10 @@
background-color: #0f2d32; background-color: #0f2d32;
} }
.dark #login { .dark #login {
background-color: #151f31; background-color: #101113;
} }
.dark h1 { .dark h1 {
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255);
} }
.ant-form-item { .ant-form-item {
margin-bottom: 16px; margin-bottom: 16px;
@@ -114,6 +121,7 @@
position: relative; position: relative;
border-radius: 25px; border-radius: 25px;
width: 100%; width: 100%;
transition: all 0.3s cubic-bezier(.645,.045,.355,1);
} }
.dark .wave-btn-bg { .dark .wave-btn-bg {
color: #fff; color: #fff;
@@ -123,7 +131,6 @@
background-origin: border-box; background-origin: border-box;
background-clip: padding-box, border-box; background-clip: padding-box, border-box;
background-size: 300%; background-size: 300%;
transition: all 0.3s cubic-bezier(.645,.045,.355,1);
width: 100%; width: 100%;
z-index: 1; z-index: 1;
} }
@@ -192,7 +199,7 @@
z-index: -1; z-index: -1;
} }
.dark .waves-header { .dark .waves-header {
background-color: #101828; background-color: #0a2227;
} }
.waves-inner-header { .waves-inner-header {
height: 50vh; height: 50vh;
@@ -204,7 +211,7 @@
position: relative; position: relative;
width: 100%; width: 100%;
height: 15vh; height: 15vh;
margin-bottom: -5px; /*Fix for safari gap*/ margin-bottom: -8px; /*Fix for safari gap*/
min-height: 100px; min-height: 100px;
max-height: 150px; max-height: 150px;
} }
@@ -212,23 +219,27 @@
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite; animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
} }
.dark .parallax > use { .dark .parallax > use {
fill: rgb(10 117 87 / 20%); fill: #0f2d32;
} }
.parallax > use:nth-child(1) { .parallax > use:nth-child(1) {
animation-delay: -2s; animation-delay: -2s;
animation-duration: 7s; animation-duration: 4s;
opacity: 0.2; opacity: 0.2;
} }
.parallax > use:nth-child(2) { .parallax > use:nth-child(2) {
animation-delay: -3s; animation-delay: -3s;
animation-duration: 10s; animation-duration: 7s;
opacity: 0.4; opacity: 0.4;
} }
.parallax > use:nth-child(3) { .parallax > use:nth-child(3) {
animation-delay: -4s; animation-delay: -4s;
animation-duration: 13s; animation-duration: 10s;
opacity: 0.6; opacity: 0.6;
} }
.parallax > use:nth-child(4) {
animation-delay: -5s;
animation-duration: 13s;
}
@keyframes move-forever { @keyframes move-forever {
0% { 0% {
transform: translate3d(-90px, 0, 0); transform: translate3d(-90px, 0, 0);
@@ -243,90 +254,216 @@
min-height: 40px; min-height: 40px;
} }
} }
.words-wrapper {
width: 100%;
display: inline-block;
position: relative;
text-align: center;
}
.words-wrapper b {
width: 100%;
display: inline-block;
position: absolute;
left: 0;
top: 0;
}
.words-wrapper b.is-visible {
position: relative;
}
.headline.zoom .words-wrapper {
-webkit-perspective: 300px;
-moz-perspective: 300px;
perspective: 300px;
}
.headline {
display: flex;
justify-content: center;
align-items: center;
}
.headline.zoom b {
opacity: 0;
}
.headline.zoom b.is-visible {
opacity: 1;
-webkit-animation: zoom-in 0.8s;
-moz-animation: zoom-in 0.8s;
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-in 0.8s;
}
.headline.zoom b.is-hidden {
-webkit-animation: zoom-out 0.8s;
-moz-animation: zoom-out 0.8s;
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-out 0.4s;
}
@-webkit-keyframes zoom-in {
0% {
opacity: 0;
-webkit-transform: translateZ(100px);
}
100% {
opacity: 1;
-webkit-transform: translateZ(0);
}
}
@-moz-keyframes zoom-in {
0% {
opacity: 0;
-moz-transform: translateZ(100px);
}
100% {
opacity: 1;
-moz-transform: translateZ(0);
}
}
@keyframes zoom-in {
0% {
opacity: 0;
-webkit-transform: translateZ(100px);
-moz-transform: translateZ(100px);
-ms-transform: translateZ(100px);
-o-transform: translateZ(100px);
transform: translateZ(100px);
}
100% {
opacity: 1;
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
}
}
@-webkit-keyframes zoom-out {
0% {
opacity: 1;
-webkit-transform: translateZ(0);
}
100% {
opacity: 0;
-webkit-transform: translateZ(-100px);
}
}
@-moz-keyframes zoom-out {
0% {
opacity: 1;
-moz-transform: translateZ(0);
}
100% {
opacity: 0;
-moz-transform: translateZ(-100px);
}
}
@keyframes zoom-out {
0% {
opacity: 1;
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
}
100% {
opacity: 0;
-webkit-transform: translateZ(-100px);
-moz-transform: translateZ(-100px);
-ms-transform: translateZ(-100px);
-o-transform: translateZ(-100px);
transform: translateZ(-100px);
}
}
</style> </style>
<body> <body>
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
<transition name="list" appear> <transition name="list" appear>
<a-layout-content class="under" style="min-height: 0;"> <a-layout-content class="under" style="min-height: 0;">
<div class="waves-header"> <div class="waves-header">
<div class="waves-inner-header"></div> <div class="waves-inner-header"></div>
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"> <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
<defs> viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" /> <defs>
</defs> <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
<g class="parallax"> </defs>
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" /> <g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" /> <use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="rgba(0, 135, 113, 0.08)" /> <use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
</g> <use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
</svg> <use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" />
</div> </g>
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;"> </svg>
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;"> </div>
<a-row type="flex" justify="center"> <a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
<a-col> <a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<h1 class="title">{{ i18n "pages.login.title" }}</h1> <a-row type="flex" justify="center">
</a-col> <a-col style="width: 100%;">
</a-row> <h1 class="title headline zoom">
<a-row type="flex" justify="center"> <span class="words-wrapper">
<a-col span="24"> <b class="is-visible">{{ i18n "pages.login.title" }}</b>
<a-form> <b>3X-UI</b>
<a-form-item> </span>
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}' </h1>
@keydown.enter.native="login" autofocus>
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
</a-input>
</a-form-item>
<a-form-item>
<password-input icon="lock" v-model.trim="user.password"
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
</password-input>
</a-form-item>
<a-form-item v-if="secretEnable">
<password-input icon="key" v-model.trim="user.loginSecret"
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
</password-input>
</a-input>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '54px' } : { display: 'inline-block' }">
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
:style="loading ? { width: '50px' } : { display: 'inline-block' }">
[[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>
</div>
</a-row>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<a-col :span="24">
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l.value" label="English" 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>
</a-col>
</a-row>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<a-col>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>&nbsp;
</a-col>
<a-col>
<theme-switch />
</a-col>
</a-row>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-col> </a-col>
</a-row> </a-row>
</a-layout-content> <a-row type="flex" justify="center">
</transition> <a-col span="24">
<a-form>
<a-form-item>
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
@keydown.enter.native="login" autofocus>
<a-icon slot="prefix" type="user" style="font-size: 16px;" />
</a-input>
</a-form-item>
<a-form-item>
<password-input icon="lock" v-model.trim="user.password" placeholder='{{ i18n "password" }}'
@keydown.enter.native="login">
</password-input>
</a-form-item>
<a-form-item v-if="secretEnable">
<password-input icon="key" v-model.trim="user.loginSecret" placeholder='{{ i18n "secretToken" }}'
@keydown.enter.native="login">
</password-input>
</a-input>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<div class="wave-btn-bg wave-btn-bg-cl"
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login"
:icon="loading ? 'poweroff' : undefined">
[[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>
</div>
</a-row>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<a-col :span="24">
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="l.value" label="English" 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>
</a-col>
</a-row>
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<a-col>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>&nbsp;
</a-col>
<a-col>
<theme-switch />
</a-col>
</a-row>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-col>
</a-row>
</a-layout-content>
</transition>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
{{template "component/themeSwitcher" .}} {{template "component/themeSwitcher" .}}
@@ -373,6 +510,42 @@
}, },
}, },
}); });
document.addEventListener("DOMContentLoaded", function() {
var animationDelay = 2000;
initHeadline();
function initHeadline() {
animateHeadline(document.querySelectorAll('.headline'));
}
function animateHeadline(headlines) {
var duration = animationDelay;
headlines.forEach(function(headline) {
setTimeout(function() {
hideWord(headline.querySelector('.is-visible'));
}, duration);
});
}
function hideWord(word) {
var nextWord = takeNext(word);
switchWord(word, nextWord);
setTimeout(function() {
hideWord(nextWord);
}, animationDelay);
}
function takeNext(word) {
return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
}
function switchWord(oldWord, newWord) {
oldWord.classList.remove('is-visible');
oldWord.classList.add('is-hidden');
newWord.classList.remove('is-hidden');
newWord.classList.add('is-visible');
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@@ -82,7 +82,7 @@
<template slot="title"> <template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template> </template>
{{ i18n "pages.inbounds.totalFlow" }} (GB) {{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
@@ -104,8 +104,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker> :dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label"> <template slot="label">
@@ -218,7 +220,7 @@
clientsBulkModal.visible = false; clientsBulkModal.visible = false;
clientsBulkModal.loading(false); clientsBulkModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
clientsBulkModal.confirmLoading = loading; clientsBulkModal.confirmLoading = loading;
}, },
}; };
@@ -234,6 +236,9 @@
get delayedExpireDays() { get delayedExpireDays() {
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0; return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
}, },
get datepicker() {
return app.datepicker;
},
set delayedExpireDays(days) { set delayedExpireDays(days) {
this.clientsBulkModal.expiryTime = -86400000 * days; this.clientsBulkModal.expiryTime = -86400000 * days;
}, },

View File

@@ -72,7 +72,7 @@
clientModal.visible = false; clientModal.visible = false;
clientModal.loading(false); clientModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
clientModal.confirmLoading = loading; clientModal.confirmLoading = loading;
}, },
}; };
@@ -94,6 +94,9 @@
get isEdit() { get isEdit() {
return this.clientModal.isEdit; return this.clientModal.isEdit;
}, },
get datepicker() {
return app.datepicker;
},
get isTrafficExhausted() { get isTrafficExhausted() {
if (!clientStats) return false if (!clientStats) return false
if (clientStats.total <= 0) return false if (clientStats.total <= 0) return false

View File

@@ -1,19 +1,19 @@
{{define "menuItems"}} {{define "menuItems"}}
<a-menu-item key="{{ .base_path }}panel/"> <a-menu-item key="{{ .base_path }}panel/">
<a-icon type="dashboard"></a-icon> <a-icon type="dashboard"></a-icon>
<span>{{ i18n "menu.dashboard"}}</span> <span><b>{{ i18n "menu.dashboard"}}</b></span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/inbounds"> <a-menu-item key="{{ .base_path }}panel/inbounds">
<a-icon type="user"></a-icon> <a-icon type="user"></a-icon>
<span>{{ i18n "menu.inbounds"}}</span> <span><b>{{ i18n "menu.inbounds"}}</b></span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/settings"> <a-menu-item key="{{ .base_path }}panel/settings">
<a-icon type="setting"></a-icon> <a-icon type="setting"></a-icon>
<span>{{ i18n "menu.settings"}}</span> <span><b>{{ i18n "menu.settings"}}</b></span>
</a-menu-item> </a-menu-item>
<a-menu-item key="{{ .base_path }}panel/xray"> <a-menu-item key="{{ .base_path }}panel/xray">
<a-icon type="tool"></a-icon> <a-icon type="tool"></a-icon>
<span>{{ i18n "menu.xray"}}</span> <span><b>{{ i18n "menu.xray"}}</b></span>
</a-menu-item> </a-menu-item>
<!--<a-menu-item key="{{ .base_path }}panel/clients">--> <!--<a-menu-item key="{{ .base_path }}panel/clients">-->
<!-- <a-icon type="laptop"></a-icon>--> <!-- <a-icon type="laptop"></a-icon>-->
@@ -21,7 +21,7 @@
<!--</a-menu-item>--> <!--</a-menu-item>-->
<a-menu-item key="{{ .base_path }}logout"> <a-menu-item key="{{ .base_path }}logout">
<a-icon type="logout"></a-icon> <a-icon type="logout"></a-icon>
<span>{{ i18n "menu.logout"}}</span> <span><b>{{ i18n "menu.logout"}}</b></span>
</a-menu-item> </a-menu-item>
{{end}} {{end}}

View File

@@ -0,0 +1,60 @@
{{define "component/persianDatepickerTemplate"}}
<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">
<template #addonAfter>
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
</template>
</a-input>
</div>
</template>
{{end}}
{{define "component/persianDatepicker"}}
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css"/>
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js"></script>
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js"></script>
<script>
const persianDatepicker = {};
Vue.component('persian-datepicker', {
props: ['placeholder', 'format', 'value'],
template: `{{template "component/persianDatepickerTemplate"}}`,
data() {
return {
date: '',
persianDatepicker,
};
},
watch: {
value: function (date) {
this.date = this.convertToJalalian(date)
}
},
mounted() {
this.date = this.convertToJalalian(this.value)
this.listenToDatepicker()
},
methods: {
convertToGregorian(date) {
return date ? moment(moment(date, 'jYYYY/jMM/jDD HH:mm:ss').format('YYYY-MM-DD HH:mm:ss')) : null
},
convertToJalalian(date) {
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null
},
listenToDatepicker() {
jalaliDatepicker.startWatch({
time: true,
zIndex: '9999',
hideAfterChange: true,
useDropDownYears: false,
changeMonthRotateYear: true,
});
},
}
});
</script>
{{end}}

View File

@@ -11,6 +11,7 @@
function createThemeSwitcher() { function createThemeSwitcher() {
const isDarkTheme = localStorage.getItem('dark-mode') === 'true'; const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
const theme = isDarkTheme ? 'dark' : 'light'; const theme = isDarkTheme ? 'dark' : 'light';
document.querySelector('body').setAttribute('class', theme)
return { return {
isDarkTheme, isDarkTheme,
get currentTheme() { get currentTheme() {
@@ -19,6 +20,8 @@
toggleTheme() { toggleTheme() {
this.isDarkTheme = !this.isDarkTheme; this.isDarkTheme = !this.isDarkTheme;
localStorage.setItem('dark-mode', this.isDarkTheme); localStorage.setItem('dark-mode', this.isDarkTheme);
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light')
document.getElementById('message').className = themeSwitcher.currentTheme;
}, },
}; };
} }
@@ -29,6 +32,10 @@
props: [], props: [],
template: `{{template "component/themeSwitchTemplate"}}`, template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({ themeSwitcher }), data: () => ({ themeSwitcher }),
mounted() {
this.$message.config({getContainer: () => document.getElementById('message')});
document.getElementById('message').className = themeSwitcher.currentTheme;
}
}); });
</script> </script>
{{end}} {{end}}

View File

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

View File

@@ -0,0 +1,57 @@
{{define "fakednsModal"}}
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
:closable="true" :mask-closable="false"
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
<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-form-item>
</a-form>
</a-modal>
<script>
const fakednsModal = {
title: '',
visible: false,
okText: '{{ i18n "confirm" }}',
isEdit: false,
confirm: null,
fakeDns: {
ipPool: "198.18.0.0/16",
poolSize: 65535,
},
ok() {
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
},
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if(isEdit) {
this.fakeDns = fakeDns;
} else {
this.fakeDns = {
ipPool: "198.18.0.0/16",
poolSize: 65535,
}
}
this.isEdit = isEdit;
},
close() {
fakednsModal.visible = false;
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#fakedns-modal',
data: {
fakednsModal: fakednsModal,
}
});
</script>
{{end}}

View File

@@ -19,7 +19,7 @@
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.client.renew" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
{{ i18n "password" }} {{ i18n "password" }}
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"@click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"@click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
@@ -32,7 +32,7 @@
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.client.renew" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon> ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
</a-tooltip> </a-tooltip>
@@ -117,7 +117,7 @@
<template slot="title"> <template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template> </template>
{{ i18n "pages.inbounds.totalFlow" }}(GB) {{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
@@ -150,8 +150,10 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker> :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>
<a-form-item v-if="client.expiryTime != 0"> <a-form-item v-if="client.expiryTime != 0">

View File

@@ -37,7 +37,7 @@
<template slot="title"> <template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template> </template>
{{ i18n "pages.inbounds.totalFlow" }}(GB) {{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
@@ -54,9 +54,11 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> v-model="dbInbound._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -95,6 +97,11 @@
{{template "form/http"}} {{template "form/http"}}
</template> </template>
<!-- wireguard -->
<template v-if="inbound.protocol === Protocols.WIREGUARD">
{{template "form/wireguard"}}
</template>
<!-- stream settings --> <!-- stream settings -->
<template v-if="inbound.canEnableStream()"> <template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}} {{template "form/streamSettings"}}
@@ -107,7 +114,7 @@
</template> </template>
<!-- sniffing --> <!-- sniffing -->
<template v-if="inbound.canSniffing()"> <template>
{{template "form/sniffing"}} {{template "form/sniffing"}}
</template> </template>
{{end}} {{end}}

View File

@@ -18,7 +18,7 @@
<a-select <a-select
v-model="outbound.settings.domainStrategy" v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Fragment'> <a-form-item label='Fragment'>
@@ -46,24 +46,114 @@
<!-- blackhole settings --> <!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole"> <template v-if="outbound.protocol === Protocols.Blackhole">
<a-form-item label='Response Type'> <a-form-item label='Response Type'>
<a-select <a-select
v-model="outbound.settings.type" v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- dns settings --> <!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS"> <template v-if="outbound.protocol === Protocols.DNS">
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> <a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select <a-select
v-model="outbound.settings.network" v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item>
</template>
<!-- wireguard settings -->
<template v-if="outbound.protocol === Protocols.Wireguard">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Workers'>
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Reserved <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model="outbound.settings.reserved"></a-input>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</template> </template>
<!-- Address + Port --> <!-- Address + Port -->
@@ -81,8 +171,8 @@
<a-form-item label='ID'> <a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input> <a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item> </a-form-item>
<!-- vless settings --> <!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()"> <template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'> <a-form-item label='Flow'>
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
@@ -94,25 +184,32 @@
<!-- Servers (trojan/shadowsocks/socks/http) settings --> <!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()"> <template v-if="outbound.hasServers()">
<!-- http / socks -->
<template v-if="outbound.hasUsername()"> <template v-if="outbound.hasUsername()">
<a-form-item label='{{ i18n "username" }}'> <a-form-item label='{{ i18n "username" }}'>
<a-input v-model.trim="outbound.settings.user"></a-input> <a-input v-model.trim="outbound.settings.user"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input> <a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item>
</template>
<!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- shadowsocks --> <!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks"> <template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'> <a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> <a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='UDP over TCP'> <a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch> <a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- stream settings --> <!-- stream settings -->
@@ -121,9 +218,9 @@
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">KCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WS</a-select-option> <a-select-option value="ws">WS</a-select-option>
<a-select-option value="http">HTTP2</a-select-option> <a-select-option value="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
</a-select> </a-select>
@@ -149,12 +246,12 @@
<template v-if="outbound.stream.network === 'kcp'"> <template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (not camouflage)</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option> <a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option> <a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option> <a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option> <a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
@@ -166,19 +263,19 @@
<a-form-item label='TTI (ms)'> <a-form-item label='TTI (ms)'>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Uplink Capacity (MB/s)'> <a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Downlink Capacity (MB/s)'> <a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch> <a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer Size (MB)'> <a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Write Buffer Size (MB)'> <a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@@ -207,9 +304,9 @@
<template v-if="outbound.stream.network === 'quic'"> <template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option> <a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option> <a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
@@ -217,12 +314,12 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (No Obfuscation)</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP (Video Call)</a-select-option> <a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP (Bittorrent)</a-select-option> <a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat Video</a-select-option> <a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option> <a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard (WireGuard Packages)</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
@@ -240,59 +337,57 @@
<!-- tls settings --> <!-- tls settings -->
<template v-if="outbound.canEnableTls()"> <template v-if="outbound.canEnableTls()">
<a-form-item label='{{ i18n "security" }}'> <a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <a-radio-group v-model="outbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-button v-if="outbound.canEnableReality()" value="reality">REALITY</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item>
<template v-if="outbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.isTls"> <a-form-item label="uTLS">
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-select v-model="outbound.stream.tls.fingerprint"
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input> :dropdown-class-name="themeSwitcher.currentTheme">
</a-form-item> <a-select-option value=''>None</a-select-option>
<a-form-item label="uTLS"> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-select v-model="outbound.stream.tls.fingerprint" </a-select>
:dropdown-class-name="themeSwitcher.currentTheme"> </a-form-item>
<a-select-option value=''>None</a-select-option> <a-form-item label="ALPN">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select mode="multiple"
</a-select> :dropdown-class-name="themeSwitcher.currentTheme"
</a-form-item> v-model="outbound.stream.tls.alpn">
<a-form-item label="ALPN"> <a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
<a-select </a-select>
mode="multiple" </a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
</template>
:dropdown-class-name="themeSwitcher.currentTheme" <!-- reality settings -->
v-model="outbound.stream.tls.alpn"> <template v-if="outbound.stream.isReality">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option> <a-form-item label="SNI">
</a-select> <a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Allow Insecure"> <a-form-item label="uTLS">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch> <a-select v-model="outbound.stream.reality.fingerprint"
</a-form-item> :dropdown-class-name="themeSwitcher.currentTheme">
</template> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
<!-- reality settings --> </a-form-item>
<template v-if="outbound.stream.isReality"> <a-form-item label="Short ID">
<a-form-item label='{{ i18n "domainName" }}'> <a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input> </a-form-item>
</a-form-item> <a-form-item label="SpiderX">
<a-form-item label="uTLS"> <a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
<a-select v-model="outbound.stream.reality.fingerprint" </a-form-item>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-form-item label="Public Key">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-select> </a-form-item>
</a-form-item> </template>
<a-form-item label="Short IDs">
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="SpiderX">
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item>
<a-form-item label="Public Key">
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
</a-form-item>
</template>
</template> </template>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>

View File

@@ -23,14 +23,14 @@
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "encryption" }}'> <a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> <a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.isSS2022"> <a-form-item v-if="inbound.isSS2022">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "password" }}</span> <span>{{ i18n "reset" }}</span>
</template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon> </template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>

View File

@@ -37,7 +37,7 @@
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/> style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='Name'> <a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input> <a-input v-model="fallback.name"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='ALPN'> <a-form-item label='ALPN'>

View File

@@ -39,7 +39,7 @@
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)" <a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/> style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider> </a-divider>
<a-form-item label='Name'> <a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input> <a-input v-model="fallback.name"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='ALPN'> <a-form-item label='ALPN'>

View File

@@ -0,0 +1,80 @@
{{define "form/wireguard"}}
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon type="sync"
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
</a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input disabled v-model="inbound.settings.pubKey"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
</a-form-item>
<a-form-item label='Kernel Mode'>
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
</a-form-item>
<a-form-item label="Peers">
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
</a-form-item>
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
Peer [[ index + 1 ]]
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
</template>
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
<a-input v-model.trim="peer.allowedIPs[index]">
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='Keep Alive'>
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
</a-form-item>
</a-form>
</a-form>
{{end}}

View File

@@ -1,6 +1,6 @@
{{define "form/sniffing"}} {{define "form/sniffing"}}
<a-divider style="margin:5px 0 0;"></a-divider> <a-divider style="margin:5px 0 0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item> <a-form-item>
<span slot="label"> <span slot="label">
Sniffing Sniffing

View File

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

View File

@@ -2,16 +2,25 @@
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (No Obfuscation)</a-select-option> <a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP (Video Call)</a-select-option> <a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP (Bittorrent)</a-select-option> <a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat Video</a-select-option> <a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option> <a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard (WireGuard packages)</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item>
<a-input v-model="inbound.stream.kcp.seed"></a-input> <template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>

View File

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

View File

@@ -1,11 +1,11 @@
{{define "form/streamSettings"}} {{define "form/streamSettings"}}
<!-- select stream network --> <!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="{{ i18n "transmission" }}"> <a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" <a-select v-model="inbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">KCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WS</a-select-option> <a-select-option value="ws">WS</a-select-option>
<a-select-option value="http">H2</a-select-option> <a-select-option value="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>

View File

@@ -1,24 +1,24 @@
{{define "form/streamSockopt"}} {{define "form/streamSockopt"}}
<a-divider style="margin:5px 0 0;"></a-divider> <a-divider style="margin:5px 0 0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Transparent Proxy"> <a-form-item label="TPROXY">
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch> <a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
</a-form-item> </a-form-item>
<template v-if="inbound.stream.sockoptSwitch"> <template v-if="inbound.stream.sockoptSwitch">
<a-form-item label="Accept Proxy Protocol"> <a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="TCP FastOpen"> <a-form-item label="TCP Fast Open">
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch> <a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Route Mark"> <a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number> <a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label="T-Proxy"> <a-form-item label="TPROXY">
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">OFF</a-select-option> <a-select-option value="off">Off</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option> <a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">T-Proxy</a-select-option> <a-select-option value="tproxy">TPROXY</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>

View File

@@ -1,58 +1,63 @@
{{define "form/streamTCP"}} {{define "form/streamTCP"}}
<!-- tcp type --> <!-- tcp type -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()"> <a-form-item label="PROXY Protocol" v-if="inbound.canEnableTls()">
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch <a-switch :checked="inbound.stream.tcp.type === 'http'"
:checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'"> @change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- tcp request --> <a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'> <!-- tcp request -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input> <a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input> <a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.requestPath" }} <template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button> <a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
</template> </template>
<template v-for="(path, index) in inbound.stream.tcp.request.path"> <template v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"> <a-input v-model.trim="inbound.stream.tcp.request.path[index]">
<a-button size="small" slot="addonAfter" <a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
@click="inbound.stream.tcp.request.removePath(index)" v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
</a-input> </a-input>
</template> </template>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button> <a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-input style="width: 50%" v-model.trim="header.value"
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input> </a-input>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<!-- tcp response -->
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'> <!-- tcp response -->
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> <a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
@@ -61,11 +66,12 @@
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'> <a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template> <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input> </a-input>
<a-input style="width: 50%" v-model.trim="header.value" <a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'> placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter"> <template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button> <a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
</template> </template>

View File

@@ -1,13 +1,13 @@
{{define "form/streamWS"}} {{define "form/streamWS"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Accept Proxy Protocol"> <a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch> <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.ws.path"></a-input> <a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button> <a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{span:24}"> <a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers"> <a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">

View File

@@ -15,7 +15,7 @@
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.realityDesc" }}</span> <span>{{ i18n "pages.inbounds.realityDesc" }}</span>
</template> </template>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button> <a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button>
</a-tooltip> </a-tooltip>
<a-radio-button value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
</a-radio-group> </a-radio-group>
@@ -26,9 +26,9 @@
<a-form-item label="SNI" placeholder="Server Name Indication"> <a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input> <a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="CipherSuites"> <a-form-item label="Cipher Suites">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">auto</a-select-option> <a-select-option value="">Auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option> <a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@@ -92,7 +92,7 @@
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input> <a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<a-form-item label='ocspStapling'> <a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number> <a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@@ -162,16 +162,16 @@
<a-form-item label='Dest'> <a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input> <a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Server Names'> <a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input> <a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.client.renew" }}</span> <span>{{ i18n "reset" }}</span>
</template> </template>
Short IDs Short ID
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon> <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
</a-icon> </a-icon>
</template> </template>
@@ -187,7 +187,7 @@
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input> <a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button> <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
</a-form-item> </a-form-item>
</template> </template>
</a-form> </a-form>

View File

@@ -40,7 +40,7 @@
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch> <a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
</template> </template>
<template slot="online" slot-scope="text, client, index"> <template slot="online" slot-scope="text, client, index">
<template v-if="isClientOnline(client.email)"> <template v-if="client.enable && isClientOnline(client.email)">
<a-tag color="green">{{ i18n "online" }}</a-tag> <a-tag color="green">{{ i18n "online" }}</a-tag>
</template> </template>
<template v-else> <template v-else>
@@ -52,7 +52,7 @@
<template slot="title"> <template slot="title">
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template> <template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template> <template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template> <template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
</template> </template>
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"> <a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
</a-badge> </a-badge>

View File

@@ -164,9 +164,9 @@
</tr> </tr>
</table> </table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId"> <template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription link</a-divider> <a-divider>Subscription URL</a-divider>
<a-row> <a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col> <a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"> <button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
@@ -175,14 +175,24 @@
</a-tooltip> </a-tooltip>
</a-col> </a-col>
</a-row> </a-row>
<a-row>
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template> </template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId"> <template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider> <a-divider>Telegram ID</a-divider>
<a-row> <a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col> <a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col>
<a-col :sx="24" :md="2" style="text-align: right;"> <a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'> <a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"> <button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)">
<a-icon type="snippets"></a-icon> <a-icon type="snippets"></a-icon>
</button> </button>
</a-tooltip> </a-tooltip>
@@ -264,6 +274,71 @@
<td><a-tag color="green">[[ account.pass ]]</a-tag></td> <td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr> </tr>
</table> </table>
<table v-if="dbInbound.isWireguard" style="margin-bottom: 10px; width: 100%;">
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ inbound.settings.secretKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ inbound.settings.pubKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>MTU</td>
<td>[[ inbound.settings.mtu ]]</td>
</tr>
<tr>
<td>Kernel Mode</td>
<td>[[ inbound.settings.kernelMode ]]</td>
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ peer.privateKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ peer.publicKey ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<td>[[ peer.psk ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<td>[[ peer.allowedIPs.join(",") ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Keep Alive</td>
<td>[[ peer.keepAlive ]]</td>
</tr>
<tr>
<td colspan="2">
<a-row>
<a-col :span="22" style="overflow-wrap: anywhere;">
<a-tag color="blue">Config</a-tag>
<div
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
style="border-radius: 1rem; padding: 0.5rem;"
class="client-table-odd-row"></div>
</a-col>
<a-col :span="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary"
:id="'copy-url-link-'+index"
@click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</td>
</tr>
</table>
</template>
</template> </template>
</a-modal> </a-modal>
<script> <script>
@@ -280,7 +355,7 @@
index: null, index: null,
isExpired: false, isExpired: false,
subLink: '', subLink: '',
tgLink: '', subJsonLink: '',
show(dbInbound, index) { show(dbInbound, index) {
this.index = index; this.index = index;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
@@ -288,13 +363,15 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null; this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry; this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : []; this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings); if (this.inbound.protocol == Protocols.WIREGUARD){
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
}
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId); this.subLink = this.genSubLink(this.clientSettings.subId);
} this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
if (this.clientSettings.tgId) {
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
} }
} }
this.visible = true; this.visible = true;
@@ -304,6 +381,9 @@
}, },
genSubLink(subID) { genSubLink(subID) {
return app.subSettings.subURI+subID; return app.subSettings.subURI+subID;
},
genSubJsonLink(subID) {
return app.subSettings.subJsonURI+subID;
} }
}; };

View File

@@ -40,7 +40,7 @@
inModal.visible = false; inModal.visible = false;
inModal.loading(false); inModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
inModal.confirmLoading = loading; inModal.confirmLoading = loading;
}, },
}; };
@@ -63,6 +63,9 @@
get client() { get client() {
return inModal.inbound.clients[0]; return inModal.inbound.clients[0];
}, },
get datepicker() {
return app.datepicker;
},
get delayedExpireDays() { get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0; return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
}, },

View File

@@ -56,9 +56,13 @@
<a-layout-content> <a-layout-content>
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear> <transition name="list" appear>
<a-tag v-if="false" color="red" style="margin-bottom: 10px"> <a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information message='{{ i18n "secAlertTitle" }}'
</a-tag> color="red"
description='{{ i18n "secAlertSsl" }}'
show-icon closable
>
</a-alert>
</transition> </transition>
<transition name="list" appear> <transition name="list" appear>
<a-card hoverable> <a-card hoverable>
@@ -133,6 +137,10 @@
<a-icon type="export"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} {{ i18n "pages.inbounds.export" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="resetInbounds"> <a-menu-item key="resetInbounds">
<a-icon type="reload"></a-icon> <a-icon type="reload"></a-icon>
{{ i18n "pages.inbounds.resetAllTraffic" }} {{ i18n "pages.inbounds.resetAllTraffic" }}
@@ -141,7 +149,7 @@
<a-icon type="file-done"></a-icon> <a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }} {{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="delDepletedClients"> <a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon> <a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }} {{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item> </a-menu-item>
@@ -178,7 +186,7 @@
</a-radio-group> </a-radio-group>
</div> </div>
<a-back-top></a-back-top> <a-back-top></a-back-top>
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id" <a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds" :data-source="searchedInbounds"
:scroll="isMobile ? {} : { x: 1000 }" :scroll="isMobile ? {} : { x: 1000 }"
:pagination=pagination(searchedInbounds) :pagination=pagination(searchedInbounds)
@@ -196,7 +204,7 @@
<a-icon type="edit"></a-icon> <a-icon type="edit"></a-icon>
{{ i18n "edit" }} {{ i18n "edit" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser"> <a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
<a-icon type="qrcode"></a-icon> <a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }} {{ i18n "qrCode" }}
</a-menu-item> </a-menu-item>
@@ -217,7 +225,11 @@
<a-icon type="export"></a-icon> <a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} {{ i18n "pages.inbounds.export"}}
</a-menu-item> </a-menu-item>
<a-menu-item key="delDepletedClients"> <a-menu-item key="subs" v-if="subSettings.enable">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon> <a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }} {{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item> </a-menu-item>
@@ -230,7 +242,7 @@
</template> </template>
<a-menu-item key="clipboard"> <a-menu-item key="clipboard">
<a-icon type="copy"></a-icon> <a-icon type="copy"></a-icon>
{{ i18n "pages.inbounds.copyToClipboard" }} {{ i18n "pages.inbounds.exportInbound" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="resetTraffic"> <a-menu-item key="resetTraffic">
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }} <a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
@@ -244,7 +256,7 @@
</span> </span>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isMobile"> <a-menu-item v-if="isMobile">
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch> <a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
{{ i18n "pages.inbounds.enable" }} {{ i18n "pages.inbounds.enable" }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -314,7 +326,7 @@
</a-popover> </a-popover>
</template> </template>
<template slot="enable" slot-scope="text, dbInbound"> <template slot="enable" slot-scope="text, dbInbound">
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
</template> </template>
<template slot="expiryTime" slot-scope="text, dbInbound"> <template slot="expiryTime" slot-scope="text, dbInbound">
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme"> <a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
@@ -446,27 +458,28 @@
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}} {{template "component/themeSwitcher" .}}
{{template "component/persianDatepicker" .}}
<script> <script>
const columns = [{ const columns = [{
title: "ID", title: "ID",
align: 'right', align: 'right',
dataIndex: "id", dataIndex: "id",
width: 40, width: 30,
responsive: ["xs"], responsive: ["xs"],
}, { }, {
title: '{{ i18n "pages.inbounds.operate" }}', title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center', align: 'center',
width: 40, width: 30,
scopedSlots: { customRender: 'action' }, scopedSlots: { customRender: 'action' },
}, { }, {
title: '{{ i18n "pages.inbounds.enable" }}', title: '{{ i18n "pages.inbounds.enable" }}',
align: 'center', align: 'center',
width: 40, width: 30,
scopedSlots: { customRender: 'enable' }, scopedSlots: { customRender: 'enable' },
}, { }, {
title: '{{ i18n "pages.inbounds.remark" }}', title: '{{ i18n "pages.inbounds.remark" }}',
align: 'center', align: 'center',
width: 80, width: 60,
dataIndex: "remark", dataIndex: "remark",
}, { }, {
title: '{{ i18n "pages.inbounds.port" }}', title: '{{ i18n "pages.inbounds.port" }}',
@@ -476,7 +489,7 @@
}, { }, {
title: '{{ i18n "pages.inbounds.protocol" }}', title: '{{ i18n "pages.inbounds.protocol" }}',
align: 'left', align: 'left',
width: 90, width: 70,
scopedSlots: { customRender: 'protocol' }, scopedSlots: { customRender: 'protocol' },
}, { }, {
title: '{{ i18n "clients" }}', title: '{{ i18n "clients" }}',
@@ -491,11 +504,11 @@
}, { }, {
title: '{{ i18n "pages.inbounds.expireDate" }}', title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center', align: 'center',
width: 60, width: 40,
scopedSlots: { customRender: 'expiryTime' }, scopedSlots: { customRender: 'expiryTime' },
}]; }];
const mobileColums = [{ const mobileColumns = [{
title: "ID", title: "ID",
align: 'right', align: 'right',
dataIndex: "id", dataIndex: "id",
@@ -539,6 +552,7 @@
data: { data: {
siderDrawer, siderDrawer,
themeSwitcher, themeSwitcher,
persianDatepicker,
spinning: false, spinning: false,
inbounds: [], inbounds: [],
dbInbounds: [], dbInbounds: [],
@@ -557,10 +571,13 @@
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000, refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: { subSettings: {
enable : false, enable : false,
subURI : '' subURI : '',
subJsonURI : '',
}, },
remarkModel: '-ieo', remarkModel: '-ieo',
datepicker: 'gregorian',
tgBotEnable: false, tgBotEnable: false,
showAlert: false,
pageSize: 0, pageSize: 0,
isMobile: window.innerWidth <= 768, isMobile: window.innerWidth <= 768,
}, },
@@ -575,6 +592,7 @@
this.refreshing = false; this.refreshing = false;
return; return;
} }
await this.getOnlineUsers(); await this.getOnlineUsers();
this.setInbounds(msg.obj); this.setInbounds(msg.obj);
setTimeout(() => { setTimeout(() => {
@@ -601,10 +619,12 @@
this.tgBotEnable = tgBotEnable; this.tgBotEnable = tgBotEnable;
this.subSettings = { this.subSettings = {
enable : subEnable, enable : subEnable,
subURI: subURI subURI: subURI,
subJsonURI: subJsonURI
}; };
this.pageSize = pageSize; this.pageSize = pageSize;
this.remarkModel = remarkModel; this.remarkModel = remarkModel;
this.datepicker = datepicker;
} }
}, },
setInbounds(dbInbounds) { setInbounds(dbInbounds) {
@@ -638,8 +658,12 @@
clientCount = clients.length; clientCount = clients.length;
if (dbInbound.enable) { if (dbInbound.enable) {
clients.forEach(client => { clients.forEach(client => {
client.enable ? active.push(client.email) : deactive.push(client.email); if (client.enable) {
if(this.isClientOnline(client.email)) online.push(client.email); active.push(client.email);
if (this.isClientOnline(client.email)) online.push(client.email);
} else {
deactive.push(client.email);
}
}); });
clientStats.forEach(client => { clientStats.forEach(client => {
if (!client.enable) { if (!client.enable) {
@@ -664,6 +688,7 @@
online: online, online: online,
}; };
}, },
searchInbounds(key) { searchInbounds(key) {
if (ObjectUtil.isEmpty(key)) { if (ObjectUtil.isEmpty(key)) {
this.searchedInbounds = this.dbInbounds.slice(); this.searchedInbounds = this.dbInbounds.slice();
@@ -727,6 +752,9 @@
case "export": case "export":
this.exportAllLinks(); this.exportAllLinks();
break; break;
case "subs":
this.exportAllSubs();
break;
case "resetInbounds": case "resetInbounds":
this.resetAllTraffic(); this.resetAllTraffic();
break; break;
@@ -758,6 +786,9 @@
case "export": case "export":
this.inboundLinks(dbInbound.id); this.inboundLinks(dbInbound.id);
break; break;
case "subs":
this.exportSubs(dbInbound.id);
break;
case "clipboard": case "clipboard":
this.copyToClipboard(dbInbound.id); this.copyToClipboard(dbInbound.id);
break; break;
@@ -807,7 +838,7 @@
protocol: baseInbound.protocol, protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(), settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(), streamSettings: baseInbound.stream.toString(),
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}', sniffing: baseInbound.sniffing.toString(),
}; };
await this.submit('/panel/inbound/add', data, inModal); await this.submit('/panel/inbound/add', data, inModal);
}, },
@@ -856,7 +887,7 @@
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
await this.submit('/panel/inbound/add', data, inModal); await this.submit('/panel/inbound/add', data, inModal);
}, },
@@ -875,7 +906,7 @@
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
}, },
@@ -963,7 +994,7 @@
}, },
delInbound(dbInboundId) { delInbound(dbInboundId) {
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}', title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "delete"}}',
@@ -976,7 +1007,7 @@
clientId = this.getClientId(dbInbound.protocol, client); clientId = this.getClientId(dbInbound.protocol, client);
if (confirmation){ if (confirmation){
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.deleteClient"}}', title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}', content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "delete"}}',
@@ -1030,8 +1061,9 @@
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
infoModal.show(newDbInbound, index); infoModal.show(newDbInbound, index);
}, },
switchEnable(dbInboundId) { switchEnable(dbInboundId,state) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
dbInbound.enable = state;
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound); this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
}, },
async switchEnableClient(dbInboundId, client) { async switchEnableClient(dbInboundId, client) {
@@ -1181,6 +1213,22 @@
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark); txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
}, },
exportSubs(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const clients = this.getInboundClients(dbInbound);
let subLinks = []
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\n'),
dbInbound.remark + "-Subs");
},
importInbound() { importInbound() {
promptModal.open({ promptModal.open({
title: '{{ i18n "pages.inbounds.importInbound" }}', title: '{{ i18n "pages.inbounds.importInbound" }}',
@@ -1193,6 +1241,23 @@
}, },
}); });
}, },
exportAllSubs() {
let subLinks = []
for (const dbInbound of this.dbInbounds) {
const clients = this.getInboundClients(dbInbound);
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\r\n'),
'All-Inbounds-Subs');
},
exportAllLinks() { exportAllLinks() {
let copyText = []; let copyText = [];
for (const dbInbound of this.dbInbounds) { for (const dbInbound of this.dbInbounds) {
@@ -1233,7 +1298,7 @@
pagination(obj){ pagination(obj){
if (this.pageSize > 0 && obj.length>this.pageSize) { if (this.pageSize > 0 && obj.length>this.pageSize) {
// Set page options based on object size // Set page options based on object size
sizeOptions = [] sizeOptions = [];
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) { for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
sizeOptions.push(i.toString()); sizeOptions.push(i.toString());
} }
@@ -1246,8 +1311,8 @@
position: 'bottom', position: 'bottom',
pageSize: this.pageSize, pageSize: this.pageSize,
pageSizeOptions: sizeOptions pageSizeOptions: sizeOptions
} };
return p return p;
} }
return false return false
}, },
@@ -1261,6 +1326,9 @@
}, 500) }, 500)
}, },
mounted() { mounted() {
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
window.addEventListener('resize', this.onResize); window.addEventListener('resize', this.onResize);
this.onResize(); this.onResize();
this.loading(); this.loading();
@@ -1298,7 +1366,6 @@
} }
}, },
}); });
</script> </script>
{{template "inboundModal"}} {{template "inboundModal"}}
@@ -1308,6 +1375,5 @@
{{template "inboundInfoModal"}} {{template "inboundInfoModal"}}
{{template "clientsModal"}} {{template "clientsModal"}}
{{template "clientsBulkModal"}} {{template "clientsBulkModal"}}
</body> </body>
</html> </html>

View File

@@ -18,6 +18,16 @@
.ant-card-dark h2 { .ant-card-dark h2 {
color: hsla(0, 0%, 100%, .65); color: hsla(0, 0%, 100%, .65);
} }
.dark .ant-card-hoverable:hover,
.dark .ant-space-item > .ant-tabs:hover {
transform: scale(0.987);
outline-color: #40434d;
}
.dark .ant-card-bordered {
outline: 2px solid var(--dark-color-background);
}
</style> </style>
<body> <body>
@@ -26,6 +36,15 @@
<a-layout id="content-layout"> <a-layout id="content-layout">
<a-layout-content> <a-layout-content>
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/> <a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
<transition name="list" appear>
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
message='{{ i18n "secAlertTitle" }}'
color="red"
description='{{ i18n "secAlertSsl" }}'
show-icon closable
>
</a-alert>
</transition>
<transition name="list" appear> <transition name="list" appear>
<a-row> <a-row>
<a-card hoverable> <a-card hoverable>
@@ -36,15 +55,15 @@
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color" :stroke-color="status.cpu.color"
:percent="status.cpu.percent"></a-progress> :percent="status.cpu.percent"></a-progress>
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div> <div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div> <div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.mem.color" :stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress> :percent="status.mem.percent"></a-progress>
<div> <div>
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]] <b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -56,7 +75,7 @@
:stroke-color="status.swap.color" :stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress> :percent="status.swap.percent"></a-progress>
<div> <div>
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]] <b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
</div> </div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
@@ -64,7 +83,7 @@
:stroke-color="status.disk.color" :stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress> :percent="status.disk.percent"></a-progress>
<div> <div>
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]] <b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -75,28 +94,28 @@
</transition> </transition>
<transition name="list" appear> <transition name="list" appear>
<a-row> <a-row>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a> <b>3X-UI:</b>
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag> <a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a> <a rel="noopener" href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@Panel3xui</a-tag></a>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "menu.link" }}: <b>{{ i18n "pages.index.operationHours" }}:</b>
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag> <a-tag color="green">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag> <a-tag color="green">OS [[ formatSecond(status.uptime) ]]</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "pages.index.xrayStatus" }}: <b>{{ i18n "pages.index.xrayStatus" }}:</b>
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag> <a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]]
</a-tag>
<a-popover v-if="status.xray.state === State.Error" <a-popover v-if="status.xray.state === State.Error"
:overlay-class-name="themeSwitcher.currentTheme"> :overlay-class-name="themeSwitcher.currentTheme">
<span slot="title" style="font-size: 12pt">Error in running xray-core <span slot="title" style="font-size: 12pt">An error occurred while running Xray
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag> <a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
</span> </span>
<template slot="content"> <template slot="content">
@@ -106,133 +125,143 @@
</a-popover> </a-popover>
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "pages.index.operationHours" }}: <b>{{ i18n "menu.link" }}:</b>
Xray: <a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
OS: <a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]] <b>{{ i18n "pages.index.systemLoad" }}:</b>
<a-tag color="green">
<a-tooltip> <a-tooltip>
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
<template slot="title"> <template slot="title">
{{ i18n "pages.index.systemLoadDesc" }} {{ i18n "pages.index.systemLoadDesc" }}
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
{{ i18n "usage"}}: <b>{{ i18n "usage"}}:</b>
RAM [[ sizeFormat(status.appStats.mem) ]] - <a-tag color="green">
Threads [[ status.appStats.threads ]] RAM [[ sizeFormat(status.appStats.mem) ]]
</a-tooltip> </a-tag>
<a-tag color="green">
Threads [[ status.appStats.threads ]]
</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
IPv4: <a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="global"></a-icon> IPv4
<template slot="title"> <template slot="title">
[[ status.publicIP.ipv4 ]] [[ status.publicIP.ipv4 ]]
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-col> </a-tag>
<a-col :span="12"> </a-col>
IPv6: <a-col :span="12">
<a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="global"></a-icon> IPv6
<template slot="title"> <template slot="title">
[[ status.publicIP.ipv6 ]] [[ status.publicIP.ipv6 ]]
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
TCP: [[ status.tcpCount ]] <a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
<template slot="title"> <template slot="title">
{{ i18n "pages.index.connectionTcpCountDesc" }} {{ i18n "pages.index.connectionTcpCountDesc" }}
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
UDP: [[ status.udpCount ]] <a-tag>
<a-tooltip> <a-tooltip>
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
<template slot="title"> <template slot="title">
{{ i18n "pages.index.connectionUdpCountDesc" }} {{ i18n "pages.index.connectionUdpCountDesc" }}
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<a-icon type="arrow-up"></a-icon> <a-tag>
[[ sizeFormat(status.netIO.up) ]]/s
<a-tooltip> <a-tooltip>
<a-icon type="arrow-up"></a-icon>
Up: [[ sizeFormat(status.netIO.up) ]]/s
<template slot="title"> <template slot="title">
{{ i18n "pages.index.upSpeed" }} {{ i18n "pages.index.upSpeed" }}
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-icon type="arrow-down"></a-icon> <a-tag>
[[ sizeFormat(status.netIO.down) ]]/s
<a-tooltip> <a-tooltip>
<a-icon type="arrow-down"></a-icon>
Down: [[ sizeFormat(status.netIO.down) ]]/s
<template slot="title"> <template slot="title">
{{ i18n "pages.index.downSpeed" }} {{ i18n "pages.index.downSpeed" }}
</template> </template>
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :md="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<a-icon type="cloud-upload"></a-icon> <a-tag>
[[ sizeFormat(status.netTraffic.sent) ]]
<a-tooltip> <a-tooltip>
<a-icon type="cloud-upload"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalSent" }} {{ i18n "pages.index.totalSent" }}
</template> </template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-icon type="cloud-download"></a-icon> <a-tag>
[[ sizeFormat(status.netTraffic.recv) ]]
<a-tooltip> <a-tooltip>
<a-icon type="cloud-download"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalReceive" }} {{ i18n "pages.index.totalReceive" }}
</template> </template> In: [[ sizeFormat(status.netTraffic.recv) ]]
<a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</a-tag>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
@@ -246,64 +275,66 @@
:closable="true" @ok="() => versionModal.visible = false" :closable="true" @ok="() => versionModal.visible = false"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
footer=""> footer="">
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2> <a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2> message='{{ i18n "pages.index.xraySwitchClickDesk" }}'
show-icon
></a-alert>
<template v-for="version, index in versionModal.versions"> <template v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" <a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
style="margin: 10px" @click="switchV2rayVersion(version)"> style="margin-right: 10px" @click="switchV2rayVersion(version)">
[[ version ]] [[ version ]]
</a-tag> </a-tag>
</template> </template>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" title="Logs" <a-modal id="log-modal" v-model="logModal.visible"
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false" :closable="true" @cancel="() => logModal.visible = false"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
width="800px" width="800px" footer="">
footer=""> <template slot="title">
{{ i18n "pages.index.logs" }}
<a-icon :spin="logModal.loading"
type="sync"
style="vertical-align: middle; margin-left: 10px;"
:disabled="logModal.loading"
@click="openLogs()">
</a-icon>
</template>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Count"> <a-form-item>
<a-select v-model="logModal.rows" <a-input-group compact>
style="width: 80px" <a-select v-model="logModal.rows" style="width:70px;"
@change="openLogs()" @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option value="10">10</a-select-option>
<a-select-option value="10">10</a-select-option> <a-select-option value="20">20</a-select-option>
<a-select-option value="20">20</a-select-option> <a-select-option value="50">50</a-select-option>
<a-select-option value="50">50</a-select-option> <a-select-option value="100">100</a-select-option>
<a-select-option value="100">100</a-select-option> </a-select>
</a-select> <a-select v-model="logModal.level" style="width:100px;"
</a-form-item> @change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form-item label="Log Level"> <a-select-option value="debug">Debug</a-select-option>
<a-select v-model="logModal.level" <a-select-option value="info">Info</a-select-option>
style="width: 120px" <a-select-option value="notice">Notice</a-select-option>
@change="openLogs()" <a-select-option value="warning">Warning</a-select-option>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-select-option value="err">Error</a-select-option>
<a-select-option value="debug">Debug</a-select-option> </a-select>
<a-select-option value="info">Info</a-select-option> </a-input-group>
<a-select-option value="notice">Notice</a-select-option>
<a-select-option value="warning">Warning</a-select-option>
<a-select-option value="err">Error</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="SysLog">
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button> <a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item style="float: right;">
<a-button type="primary" style="margin-bottom: 10px;" <a-button type="primary" icon="download"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log"> :href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
{{ i18n "download" }} x-ui.log
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.formattedLogs"></div>
</a-modal> </a-modal>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
:closable="true" :class="themeSwitcher.currentTheme" :closable="true" footer=""
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()"> :class="themeSwitcher.currentTheme">
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content" <a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
:message="backupModal.description" :message="backupModal.description"
show-icon show-icon
@@ -426,14 +457,15 @@
const logModal = { const logModal = {
visible: false, visible: false,
logs: '', logs: [],
rows: 20, rows: 20,
level: 'info', level: 'info',
syslog: false, syslog: false,
loading: false, loading: false,
show(logs) { show(logs) {
this.visible = true; this.visible = true;
this.logs = logs? this.formatLogs(logs) : "No Record..."; this.logs = logs;
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
}, },
formatLogs(logs) { formatLogs(logs) {
let formattedLogs = ''; let formattedLogs = '';
@@ -511,6 +543,7 @@
backupModal, backupModal,
spinning: false, spinning: false,
loadingTip: '{{ i18n "loading"}}', loadingTip: '{{ i18n "loading"}}',
showAlert: false,
}, },
methods: { methods: {
loading(spinning, tip = '{{ i18n "loading"}}') { loading(spinning, tip = '{{ i18n "loading"}}') {
@@ -634,14 +667,14 @@
}, },
}, },
async mounted() { async mounted() {
let retries = 0; if (window.location.protocol !== "https:") {
while (retries < 5) { this.showAlert = true;
}
while (true) {
try { try {
await this.getStatus(); await this.getStatus();
retries = 0;
} catch (e) { } catch (e) {
console.error("Error occurred while fetching status:", e); console.error(e);
retries++;
} }
await PromiseUtil.sleep(2000); await PromiseUtil.sleep(2000);
} }

View File

@@ -75,28 +75,43 @@
<a-layout id="content-layout"> <a-layout id="content-layout">
<a-layout-content> <a-layout-content>
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'> <a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
<transition name="list" appear>
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
message='{{ i18n "secAlertTitle" }}'
color="red"
description='{{ i18n "secAlertSsl" }}'
show-icon closable
>
</a-alert>
<a-alert type="error" v-if="confAlerts.length>0" style="margin-bottom: 10px"
message='{{ i18n "secAlertTitle" }}'
color="red"
show-icon closable
>
<template slot="description">
{{ i18n "secAlertConf" }}
<li v-for="a in confAlerts">- [[ a ]]</li>
</template>
</a-alert>
</transition>
<a-space direction="vertical"> <a-space direction="vertical">
<a-card hoverable style="margin-bottom: .5rem;"> <a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
<a-row> <a-row style="display: flex; flex-wrap: wrap; align-items: center;">
<a-col :xs="24" :sm="8" style="padding: 4px;"> <a-col :xs="24" :sm="10" style="padding: 4px;">
<a-space direction="horizontal"> <a-space direction="horizontal">
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button> <a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button> <a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
</a-space> </a-space>
</a-col> </a-col>
<a-col :xs="24" :sm="16"> <a-col :xs="24" :sm="14">
<template> <template>
<div> <div>
<template> <a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
<div> </a-back-top>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"> <a-alert type="warning" style="float: right; width: fit-content"
</a-back-top> message='{{ i18n "pages.settings.infoDesc" }}'
<a-alert type="warning" style="float: right; width: fit-content" show-icon
message='{{ i18n "pages.settings.infoDesc" }}' >
show-icon
>
</div>
</template>
</div> </div>
</template> </template>
</a-col> </a-col>
@@ -141,9 +156,29 @@
<a-list-item> <a-list-item>
<a-row style="padding: 20px"> <a-row style="padding: 20px">
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta title="Language" /> <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>
<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-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select <a-select
@@ -166,42 +201,22 @@
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;"> <a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider> <a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
<a-form style="padding: 20px;" layout="inline"> <a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
<table cellpadding="2"> <a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
<tr> <a-input v-model="user.oldUsername"></a-input>
<td>{{ i18n "pages.settings.oldUsername"}}:</td> </a-form-item>
<td> <a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
<a-form-item> <password-input v-model="user.oldPassword"></password-input>
<a-input v-model="user.oldUsername" style="width: 200px"></a-input> </a-form-item>
</a-form-item> <a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
</td> <a-input v-model="user.newUsername"></a-input>
</tr> </a-form-item>
<tr> <a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
<td>{{ i18n "pages.settings.currentPassword"}}:</td> <password-input v-model="user.newPassword"></password-input>
<td> </a-form-item>
<a-form-item> <a-form-item label=" ">
<password-input v-model="user.oldPassword" style="width: 200px"></password-input> <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.settings.newUsername"}}:</td>
<td>
<a-form-item>
<a-input v-model="user.newUsername" style="width: 200px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.settings.newPassword"}}:</td>
<td>
<a-form-item>
<password-input v-model="user.newPassword" style="width: 200px"></password-input>
</a-form-item>
</td>
</tr>
</table>
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
</a-form> </a-form>
<a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider> <a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider>
<a-form style="padding: 20px;"> <a-form style="padding: 20px;">
@@ -233,7 +248,6 @@
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button> <a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
<a-list item-layout="horizontal"> <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="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
@@ -243,20 +257,19 @@
<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.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="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="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>
<a-list-item> <a-list-item>
<a-row style="padding: 20px"> <a-row style="padding: 20px">
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<a-list-item-meta title="Telegram Bot Language" /> <a-list-item-meta title="Telegram Bot Language" />
</a-col> </a-col>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select <a-select
ref="selectBotLang" ref="selectBotLang"
v-model="allSetting.tgLang" v-model="allSetting.tgLang"
:dropdown-class-name="themeSwitcher.currentTheme" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%" style="width: 100%">
>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <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> <span role="img" :aria-label="l.name" v-text="l.icon"></span>
&nbsp;&nbsp;<span v-text="l.name"></span> &nbsp;&nbsp;<span v-text="l.name"></span>
@@ -283,6 +296,17 @@
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </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>
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
<template v-if="fragment">
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</template>
</a-list>
</a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
</a-spin> </a-spin>
@@ -311,7 +335,26 @@
showAlert: false, showAlert: false,
remarkModels: {i:'Inbound',e:'Email',o:'Other'}, remarkModels: {i:'Inbound',e:'Email',o:'Other'},
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'], remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
remarkSample: '', remarkSample: '',
defaultFragment: {
tag: "fragment",
protocol: "freedom",
settings: {
domainStrategy: "AsIs",
fragment: {
packets: "tlshello",
length: "100-200",
interval: "10-20"
}
},
streamSettings: {
sockopt: {
tcpKeepAliveIdle: 100,
tcpNoDelay: true
}
}
},
get remarkModel() { get remarkModel() {
rm = this.allSetting.remarkModel; rm = this.allSetting.remarkModel;
return rm.length>1 ? rm.substring(1).split('') : []; return rm.length>1 ? rm.substring(1).split('') : [];
@@ -328,6 +371,12 @@
this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1); this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1);
this.changeRemarkSample(); this.changeRemarkSample();
}, },
get datepicker() {
return this.allSetting.datepicker ? this.allSetting.datepicker : 'gregorian';
},
set datepicker(value) {
this.allSetting.datepicker = value;
},
changeRemarkSample(){ changeRemarkSample(){
sample = [] sample = []
this.remarkModel.forEach(r => sample.push(this.remarkModels[r])); this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
@@ -384,9 +433,7 @@
if (msg.success) { if (msg.success) {
this.loading(true); this.loading(true);
await PromiseUtil.sleep(5000); await PromiseUtil.sleep(5000);
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting; let { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
if (host == this.oldAllSetting.webDomain) host = null;
if (port == this.oldAllSetting.webPort) port = null;
const isTLS = webCertFile !== "" || webKeyFile !== ""; const isTLS = webCertFile !== "" || webKeyFile !== "";
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" }); const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
window.location.replace(url); window.location.replace(url);
@@ -436,13 +483,60 @@
} }
}, },
}, },
async mounted() { computed: {
await this.getAllSetting(); fragment: {
while (true) { get: function() { return this.allSetting?.subJsonFragment != ""; },
await PromiseUtil.sleep(600); set: function (v) {
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting); this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
}
},
fragmentLength: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.length = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentInterval: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.interval = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
confAlerts: {
get: function() {
if (!this.allSetting) return [];
var alerts = []
if (this.allSetting.port == 54321) alerts.push('{{ i18n "pages.settings.panelPort"}}');
panelPath = window.location.pathname.split('/').length<4
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelSettings"}} {{ i18n "pages.settings.panelUrlPath"}}');
if (this.allSetting.subEnable) {
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
if (subPath == '/sub/') alerts.push('{{ i18n "pages.settings.subSettings"}} {{ i18n "pages.settings.subPath"}}');
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
if (subJsonPath == '/json/') alerts.push('JSON {{ i18n "pages.settings.subPath"}}');
}
return alerts
}
} }
}, },
async mounted() {
if (window.location.protocol !== "https:") {
this.showAlert = true;
}
await this.getAllSetting();
while (true) {
await PromiseUtil.sleep(1000);
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
}
}
}); });
</script> </script>
</body> </body>

View File

@@ -0,0 +1,205 @@
{{define "warpModal"}}
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
:footer="null" :class="themeSwitcher.currentTheme">
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.create" }}</a-button>
</template>
<template v-else>
<table style="margin: 5px 0; width: 100%;">
<tr class="client-table-odd-row">
<td>Access Token</td>
<td>[[ warpModal.warpData.access_token ]]</td>
</tr>
<tr>
<td>Devide ID</td>
<td>[[ warpModal.warpData.device_id ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>License Key</td>
<td>[[ warpModal.warpData.license_key ]]</td>
</tr>
<tr>
<td>Private Key</td>
<td>[[ warpModal.warpData.private_key ]]</td>
</tr>
</table>
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.modifySettings" }}</a-divider>
<a-collapse style="margin: 10px 0;">
<a-collapse-panel header='WARP/WARP+ License Key'>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="License Key">
<a-input v-model="warpPlus"></a-input>
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
</a-form-item>
</a-form>
</a-collapse-panel>
</a-collapse>
<a-divider style="margin: 0;">{{ i18n "pages.settings.toasts.getSettings" }}</a-divider>
<a-button icon="sync" @click="getConfig" style="margin-bottom: 10px;" :loading="warpModal.confirmLoading">{{ i18n "info" }}</a-button>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
<table style="width: 100%">
<tr class="client-table-odd-row">
<td>Device Name</td>
<td>[[ warpModal.warpConfig.name ]]</td>
</tr>
<tr>
<td>Device Model</td>
<td>[[ warpModal.warpConfig.model ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Device Active</td>
<td>[[ warpModal.warpConfig.enabled ]]</td>
</tr>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
<tr>
<td>Account Type</td>
<td>[[ warpModal.warpConfig.account.account_type ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Role</td>
<td>[[ warpModal.warpConfig.account.role ]]</td>
</tr>
<tr>
<td>Premium Data</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>Quota</td>
<td>[[ 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>
</tr>
</template>
</table>
<a-divider style="margin: 10px 0;">WARP {{ i18n "pages.xray.rules.outbound" }}</a-divider>
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "status" }}'>
<template v-if="warpOutboundIndex>=0">
<a-tag color="green">{{ i18n "enabled" }}</a-tag>
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading">{{ i18n "reset" }}</a-button>
</template>
<template v-else>
<a-tag color="orange">{{ i18n "disabled" }}</a-tag>
<a-button @click="addOutbound" :loading="warpModal.confirmLoading">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
</template>
</a-form-item>
</a-form>
</template>
</template>
</a-modal>
<script>
const warpModal = {
visible: false,
confirmLoading: false,
warpData: null,
warpConfig: null,
warpOutbound: null,
show() {
this.visible = true;
this.warpConfig = null;
this.getData();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading=true) {
this.confirmLoading = loading;
},
async getData(){
this.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/data');
this.loading(false);
if (msg.success) {
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
}
},
};
new Vue({
delimiters: ['[[', ']]'],
el: '#warp-modal',
data: {
warpModal: warpModal,
warpPlus: '',
},
methods: {
collectConfig() {
config = warpModal.warpConfig.config;
peer = config.peers[0];
if(config){
warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp',
protocol: Protocols.Wireguard,
settings: {
mtu: 1420,
secretKey: warpModal.warpData.private_key,
address: Object.values(config.interface.addresses),
domainStrategy: 'ForceIP',
peers: [{
publicKey: peer.public_key,
endpoint: peer.endpoint.host,
}],
kernelMode: false
}
});
}
},
async register(){
warpModal.loading(true);
keys = Wireguard.generateKeypair();
const msg = await HttpUtil.post('/panel/xray/warp/reg',keys);
if (msg.success) {
resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data;
warpModal.warpConfig = resp.config;
this.collectConfig();
}
warpModal.loading(false);
},
async updateLicense(l){
warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/license',{license: l});
if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null;
this.warpPlus = '';
}
warpModal.loading(false);
},
async getConfig(){
warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/config');
warpModal.loading(false);
if (msg.success) {
warpModal.warpConfig = JSON.parse(msg.obj);
this.collectConfig();
}
},
addOutbound(){
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
},
resetOutbound(){
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close();
}
},
computed: {
warpOutboundIndex: {
get: function() {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
}
}
}
});
</script>
{{end}}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
{{define "balancerModal"}}
<a-modal
id="balancer-modal"
v-model="balancerModal.visible"
:title="balancerModal.title"
@ok="balancerModal.ok"
:confirm-loading="balancerModal.confirmLoading"
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
:closable="true"
:mask-closable="false"
:ok-text="balancerModal.okText"
cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
placeholder='{{ i18n "pages.xray.balancer.tagDesc" }}'></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="random">Random</a-select-option>
<a-select-option value="roundRobin">Round Robin</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
:validate-status="balancerModal.emptySelector? 'warning' : 'success'">
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
<script>
const balancerModal = {
title: '',
visible: false,
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
confirm: null,
duplicateTag: false,
emptySelector: false,
balancer: {
tag: '',
strategy: 'random',
selector: []
},
outboundTags: [],
balancerTags:[],
ok() {
if (balancerModal.balancer.selector.length == 0) {
balancerModal.emptySelector = true;
return;
}
balancerModal.emptySelector = false;
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
},
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
balancerModal.balancer = balancer;
} else {
balancerModal.balancer = {
tag: '',
strategy: 'random',
selector: []
};
}
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
this.isEdit = isEdit;
this.check();
this.checkSelector();
},
close() {
this.visible = false;
this.loading(false);
},
loading(loading=true) {
this.confirmLoading = loading;
},
check() {
if (this.balancer.tag == '' || this.balancerTags.includes(this.balancer.tag)) {
this.duplicateTag = true;
this.isValid = false;
} else {
this.duplicateTag = false;
this.isValid = true;
}
},
checkSelector() {
this.emptySelector = this.balancer.selector.length == 0;
}
};
new Vue({
delimiters: ['[[', ']]'],
el: '#balancer-modal',
data: {
balancerModal: balancerModal
},
methods: {
}
});
</script>
{{end}}

View File

@@ -42,7 +42,7 @@
outModal.visible = false; outModal.visible = false;
outModal.loading(false); outModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
outModal.confirmLoading = loading; outModal.confirmLoading = loading;
}, },
check(){ check(){

View File

@@ -120,7 +120,7 @@
reverseModal.visible = false; reverseModal.visible = false;
reverseModal.loading(false); reverseModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
reverseModal.confirmLoading = loading; reverseModal.confirmLoading = loading;
}, },
}; };
@@ -132,8 +132,6 @@
reverseModal: reverseModal, reverseModal: reverseModal,
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'}, reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
}, },
methods: {
}
}); });
</script> </script>

View File

@@ -20,7 +20,7 @@
<a-input v-model.trim="ruleModal.rule.source"></a-input> <a-input v-model.trim="ruleModal.rule.source"></a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template slot="label">Source Port <template slot="label">
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span> <span>{{ i18n "pages.xray.rules.useComma" }}</span>
@@ -36,8 +36,8 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Protocol'> <a-form-item label='Protocol'>
<a-select v-model="ruleModal.rule.protocol" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option> <a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='Attributes'> <a-form-item label='Attributes'>
@@ -107,6 +107,19 @@
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table> </table>
</a-form> </a-form>
</a-modal> </a-modal>
@@ -133,11 +146,12 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
}, },
inboundTags: [], inboundTags: [],
outboundTags: [], outboundTags: [],
users: [], users: [],
balancerTag: [], balancerTags: [],
ok() { ok() {
newRule = ruleModal.getResult(); newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule); ObjectUtil.execute(ruleModal.confirm, newRule);
@@ -160,6 +174,7 @@
this.rule.protocol = rule.protocol; this.rule.protocol = rule.protocol;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : []; this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
this.rule.outboundTag = rule.outboundTag; this.rule.outboundTag = rule.outboundTag;
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
} else { } else {
this.rule = { this.rule = {
domainMatcher: "", domainMatcher: "",
@@ -174,24 +189,29 @@
protocol: [], protocol: [],
attrs: [], attrs: [],
outboundTag: "", outboundTag: "",
balancerTag: "",
} }
} }
this.isEdit = isEdit; this.isEdit = isEdit;
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag); this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
if(app.templateSettings.reverse){ if(app.templateSettings.reverse){
if(app.templateSettings.reverse.bridges) { if(app.templateSettings.reverse.bridges) {
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag)); this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
} }
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag)); if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
} }
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
}
}, },
close() { close() {
ruleModal.visible = false; ruleModal.visible = false;
ruleModal.loading(false); ruleModal.loading(false);
}, },
loading(loading) { loading(loading=true) {
ruleModal.confirmLoading = loading; ruleModal.confirmLoading = loading;
}, },
getResult() { getResult() {
@@ -210,7 +230,8 @@
rule.inboundTag = value.inboundTag; rule.inboundTag = value.inboundTag;
rule.protocol = value.protocol; rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs); rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag; rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
for (const [key, value] of Object.entries(rule)) { for (const [key, value] of Object.entries(rule)) {
if ( if (

View File

@@ -1,7 +1,9 @@
package job package job
import ( import (
"bufio"
"encoding/json" "encoding/json"
"io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@@ -12,18 +14,22 @@ import (
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/xray" "x-ui/xray"
) )
type CheckClientIpJob struct{} type CheckClientIpJob struct {
disAllowedIps []string
}
var job *CheckClientIpJob var job *CheckClientIpJob
var disAllowedIps []string
var ipFiles = []string{ var ipFiles = []string{
xray.GetIPLimitLogPath(), xray.GetIPLimitLogPath(),
xray.GetIPLimitBannedLogPath(), xray.GetIPLimitBannedLogPath(),
xray.GetIPLimitBannedPrevLogPath(),
xray.GetAccessPersistentLogPath(), xray.GetAccessPersistentLogPath(),
xray.GetAccessPersistentPrevLogPath(),
} }
func NewCheckClientIpJob() *CheckClientIpJob { func NewCheckClientIpJob() *CheckClientIpJob {
@@ -33,8 +39,10 @@ func NewCheckClientIpJob() *CheckClientIpJob {
func (j *CheckClientIpJob) Run() { func (j *CheckClientIpJob) Run() {
// create files required for iplimit if not exists // create files and dirs required for iplimit if not exists
for i := 0; i < len(ipFiles); i++ { for i := 0; i < len(ipFiles); i++ {
err := os.MkdirAll(config.GetLogFolder(), 0770)
j.checkError(err)
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
j.checkError(err) j.checkError(err)
defer file.Close() defer file.Close()
@@ -45,6 +53,37 @@ func (j *CheckClientIpJob) Run() {
j.checkFail2BanInstalled() j.checkFail2BanInstalled()
j.processLogFile() j.processLogFile()
} }
if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
go j.clearLogTime()
}
}
func (j *CheckClientIpJob) clearLogTime() {
for {
time.Sleep(time.Hour)
j.clearAccessLog()
}
}
func (j *CheckClientIpJob) clearAccessLog() {
accessLogPath := xray.GetAccessLogPath()
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
j.checkError(err)
defer logAccessP.Close()
// reopen the access log file for reading
file, err := os.Open(accessLogPath)
j.checkError(err)
defer file.Close()
// copy access log content to persistent file
_, err = io.Copy(logAccessP, file)
j.checkError(err)
// clean access log
err = os.Truncate(accessLogPath, 0)
j.checkError(err)
} }
func (j *CheckClientIpJob) hasLimitIp() bool { func (j *CheckClientIpJob) hasLimitIp() bool {
@@ -88,24 +127,34 @@ func (j *CheckClientIpJob) checkFail2BanInstalled() {
func (j *CheckClientIpJob) processLogFile() { func (j *CheckClientIpJob) processLogFile() {
accessLogPath := xray.GetAccessLogPath() accessLogPath := xray.GetAccessLogPath()
if accessLogPath == "" {
logger.Warning("access.log doesn't exist in your config.json") if accessLogPath == "none" {
logger.Warning("Access log is set to 'none' check your Xray Configs")
return return
} }
data, err := os.ReadFile(accessLogPath) if accessLogPath == "" {
InboundClientIps := make(map[string][]string) logger.Warning("Access log doesn't exist in your Xray Configs")
j.checkError(err) return
}
lines := strings.Split(string(data), "\n") file, err := os.Open(accessLogPath)
for _, line := range lines { j.checkError(err)
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) defer file.Close()
InboundClientIps := make(map[string][]string)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
ipRegx, _ := regexp.Compile(`(\d+\.\d+\.\d+\.\d+).* accepted`)
emailRegx, _ := regexp.Compile(`email:.+`) emailRegx, _ := regexp.Compile(`email:.+`)
matchesIp := ipRegx.FindString(line) matches := ipRegx.FindStringSubmatch(line)
if len(matchesIp) > 0 { if len(matches) > 1 {
ip := string(matchesIp) ip := matches[1]
if ip == "127.0.0.1" || ip == "1.1.1.1" { if ip == "127.0.0.1" {
continue continue
} }
@@ -120,14 +169,14 @@ func (j *CheckClientIpJob) processLogFile() {
continue continue
} }
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
} else { } else {
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip) InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
} }
} }
} }
disAllowedIps = []string{} j.checkError(scanner.Err())
shouldCleanLog := false shouldCleanLog := false
for clientEmail, ips := range InboundClientIps { for clientEmail, ips := range InboundClientIps {
@@ -138,27 +187,13 @@ func (j *CheckClientIpJob) processLogFile() {
} else { } else {
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips) shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
} }
} }
// added delay before cleaning logs to reduce chance of logging IP that already has been banned // added delay before cleaning logs to reduce chance of logging IP that already has been banned
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
if shouldCleanLog { if shouldCleanLog {
// copy access log to persistent file j.clearAccessLog()
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
j.checkError(err)
input, err := os.ReadFile(accessLogPath)
j.checkError(err)
if _, err := logAccessP.Write(input); err != nil {
j.checkError(err)
}
defer logAccessP.Close()
// clean access log
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
j.checkError(err)
}
} }
} }
@@ -234,6 +269,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
json.Unmarshal([]byte(inbound.Settings), &settings) json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"] clients := settings["clients"]
shouldCleanLog := false shouldCleanLog := false
j.disAllowedIps = []string{}
// create iplimit log file channel // create iplimit log file channel
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
@@ -252,7 +288,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
shouldCleanLog = true shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable { if limitIp < len(ips) && inbound.Enable {
disAllowedIps = append(disAllowedIps, ips[limitIp:]...) j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
for i := limitIp; i < len(ips); i++ { for i := limitIp; i < len(ips); i++ {
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i]) log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
} }
@@ -260,8 +296,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
} }
} }
} }
logger.Debug("disAllowedIps ", disAllowedIps)
sort.Strings(disAllowedIps) sort.Strings(j.disAllowedIps)
if len(j.disAllowedIps) > 0 {
logger.Debug("disAllowedIps ", j.disAllowedIps)
}
db := database.GetDB() db := database.GetDB()
err = db.Save(inboundClientIps).Error err = db.Save(inboundClientIps).Error

View File

@@ -1,6 +1,9 @@
package job package job
import "x-ui/web/service" import (
"x-ui/logger"
"x-ui/web/service"
)
type CheckXrayRunningJob struct { type CheckXrayRunningJob struct {
xrayService service.XrayService xrayService service.XrayService
@@ -15,11 +18,15 @@ func NewCheckXrayRunningJob() *CheckXrayRunningJob {
func (j *CheckXrayRunningJob) Run() { func (j *CheckXrayRunningJob) Run() {
if j.xrayService.IsXrayRunning() { if j.xrayService.IsXrayRunning() {
j.checkTime = 0 j.checkTime = 0
return } else {
j.checkTime++
//only restart if it's down 2 times in a row
if j.checkTime > 1 {
err := j.xrayService.RestartXray(false)
j.checkTime = 0
if err != nil {
logger.Error("Restart xray failed:", err)
}
}
} }
j.checkTime++
if j.checkTime < 2 {
return
}
j.xrayService.SetToNeedRestart()
} }

View File

@@ -15,10 +15,38 @@ func NewClearLogsJob() *ClearLogsJob {
// Here Run is an interface method of the Job interface // Here Run is an interface method of the Job interface
func (j *ClearLogsJob) Run() { func (j *ClearLogsJob) Run() {
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()} logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
// clear log files // clear old previous logs
for i := 0; i < len(logFilesPrev); i++ {
if err := os.Truncate(logFilesPrev[i], 0); err != nil {
logger.Warning("clear logs job err:", err)
}
}
// clear log files and copy to previous logs
for i := 0; i < len(logFiles); i++ { for i := 0; i < len(logFiles); i++ {
if err := os.Truncate(logFiles[i], 0); err != nil { if i > 0 {
// copy to previous logs
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
logger.Warning("clear logs job err:", err)
}
logFile, err := os.ReadFile(logFiles[i])
if err != nil {
logger.Warning("clear logs job err:", err)
}
_, err = logFilePrev.Write(logFile)
if err != nil {
logger.Warning("clear logs job err:", err)
}
defer logFilePrev.Close()
}
err := os.Truncate(logFiles[i], 0)
if err != nil {
logger.Warning("clear logs job err:", err) logger.Warning("clear logs job err:", err)
} }
} }

View File

@@ -6,8 +6,9 @@ import (
) )
type XrayTrafficJob struct { type XrayTrafficJob struct {
xrayService service.XrayService xrayService service.XrayService
inboundService service.InboundService inboundService service.InboundService
outboundService service.OutboundService
} }
func NewXrayTrafficJob() *XrayTrafficJob { func NewXrayTrafficJob() *XrayTrafficJob {
@@ -24,11 +25,15 @@ func (j *XrayTrafficJob) Run() {
logger.Warning("get xray traffic failed:", err) logger.Warning("get xray traffic failed:", err)
return return
} }
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics) err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
if err != nil { if err != nil {
logger.Warning("add traffic failed:", err) logger.Warning("add inbound traffic failed:", err)
} }
if needRestart { err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
if err != nil {
logger.Warning("add outbound traffic failed:", err)
}
if needRestart0 || needRestart1 {
j.xrayService.SetToNeedRestart() j.xrayService.SetToNeedRestart()
} }

View File

@@ -1,7 +1,9 @@
{ {
"log": { "log": {
"loglevel": "warning", "access": "none",
"error": "./error.log" "dnsLog": false,
"error": "./error.log",
"loglevel": "warning"
}, },
"api": { "api": {
"tag": "api", "tag": "api",
@@ -24,6 +26,7 @@
], ],
"outbounds": [ "outbounds": [
{ {
"tag": "direct",
"protocol": "freedom", "protocol": "freedom",
"settings": {} "settings": {}
}, },
@@ -42,7 +45,9 @@
}, },
"system": { "system": {
"statsInboundDownlink": true, "statsInboundDownlink": true,
"statsInboundUplink": true "statsInboundUplink": true,
"statsOutboundDownlink": true,
"statsOutboundUplink": true
} }
}, },
"routing": { "routing": {

View File

@@ -38,9 +38,25 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
return inbounds, nil return inbounds, nil
} }
func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) { func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
db := database.GetDB() db := database.GetDB()
db = db.Model(model.Inbound{}).Where("port = ?", port) if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
db = db.Model(model.Inbound{}).Where("port = ?", port)
} else {
db = db.Model(model.Inbound{}).
Where("port = ?", port).
Where(
db.Model(model.Inbound{}).Where(
"listen = ?", listen,
).Or(
"listen = \"\"",
).Or(
"listen = \"0.0.0.0\"",
).Or(
"listen = \"::\"",
).Or(
"listen = \"::0\""))
}
if ignoreId > 0 { if ignoreId > 0 {
db = db.Where("id != ?", ignoreId) db = db.Where("id != ?", ignoreId)
} }
@@ -135,7 +151,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
} }
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Port, 0) exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -252,7 +268,7 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
} }
func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
exist, err := s.checkPortExist(inbound.Port, inbound.Id) exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id)
if err != nil { if err != nil {
return inbound, false, err return inbound, false, err
} }
@@ -295,7 +311,11 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing oldInbound.Sniffing = inbound.Sniffing
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
}
needRestart := false needRestart := false
s.xrayApi.Init(p.GetAPIPort()) s.xrayApi.Init(p.GetAPIPort())
@@ -488,6 +508,10 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
} }
} }
if len(newClients) == 0 {
return false, common.NewError("no client remained in Inbound")
}
settings["clients"] = newClients settings["clients"] = newClients
newSettings, err := json.MarshalIndent(settings, "", " ") newSettings, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
@@ -658,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return needRestart, tx.Save(oldInbound).Error return needRestart, tx.Save(oldInbound).Error
} }
func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
var err error var err error
db := database.GetDB() db := database.GetDB()
tx := db.Begin() tx := db.Begin()
@@ -670,7 +694,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
tx.Commit() tx.Commit()
} }
}() }()
err = s.addInboundTraffic(tx, inboundTraffics) err = s.addInboundTraffic(tx, traffics)
if err != nil { if err != nil {
return err, false return err, false
} }
@@ -1146,6 +1170,8 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { if inbound.Protocol == "trojan" {
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else { } else {
clientId = oldClient.ID clientId = oldClient.ID
} }
@@ -1184,6 +1210,32 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
return nil return nil
} }
func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) {
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
if err != nil {
return false, err
}
if inbound == nil {
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
}
clients, err := s.GetClients(inbound)
if err != nil {
return false, err
}
isEnable := false
for _, client := range clients {
if client.Email == clientEmail {
isEnable = client.Enable
break
}
}
return isEnable, err
}
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) { func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
_, inbound, err := s.GetClientInboundByEmail(clientEmail) _, inbound, err := s.GetClientInboundByEmail(clientEmail)
if err != nil { if err != nil {
@@ -1205,6 +1257,8 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, er
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { if inbound.Protocol == "trojan" {
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else { } else {
clientId = oldClient.ID clientId = oldClient.ID
} }
@@ -1266,6 +1320,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { if inbound.Protocol == "trojan" {
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else { } else {
clientId = oldClient.ID clientId = oldClient.ID
} }
@@ -1324,6 +1380,8 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { if inbound.Protocol == "trojan" {
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else { } else {
clientId = oldClient.ID clientId = oldClient.ID
} }
@@ -1385,6 +1443,8 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { if inbound.Protocol == "trojan" {
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else { } else {
clientId = oldClient.ID clientId = oldClient.ID
} }
@@ -1613,10 +1673,10 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
return nil return nil
} }
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) { func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tguname)).Find(&inbounds).Error err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tgId)).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound { if err != nil && err != gorm.ErrRecordNotFound {
return nil, err return nil, err
} }
@@ -1627,7 +1687,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
logger.Error("Unable to get clients from inbound") logger.Error("Unable to get clients from inbound")
} }
for _, client := range clients { for _, client := range clients {
if client.TgID == tguname { if client.TgID == tgId {
emails = append(emails, client.Email) emails = append(emails, client.Email)
} }
} }
@@ -1840,6 +1900,13 @@ func (s *InboundService) MigrationRequirements() {
newStream, _ := json.MarshalIndent(stream, " ", " ") newStream, _ := json.MarshalIndent(stream, " ", " ")
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream) tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
} }
err = tx.Raw(`UPDATE inbounds
SET tag = REPLACE(tag, '0.0.0.0:', '')
WHERE INSTR(tag, '0.0.0.0:') > 0;`).Error
if err != nil {
return
}
} }
func (s *InboundService) MigrateDB() { func (s *InboundService) MigrateDB() {

101
web/service/outbound.go Normal file
View File

@@ -0,0 +1,101 @@
package service
import (
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/xray"
"gorm.io/gorm"
)
type OutboundService struct {
}
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
var err error
db := database.GetDB()
tx := db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
err = s.addOutboundTraffic(tx, traffics)
if err != nil {
return err, false
}
return nil, false
}
func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
if len(traffics) == 0 {
return nil
}
var err error
for _, traffic := range traffics {
if traffic.IsOutbound {
var outbound model.OutboundTraffics
err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
FirstOrCreate(&outbound).Error
if err != nil {
return err
}
outbound.Tag = traffic.Tag
outbound.Up = outbound.Up + traffic.Up
outbound.Down = outbound.Down + traffic.Down
outbound.Total = outbound.Up + outbound.Down
err = tx.Save(&outbound).Error
if err != nil {
return err
}
}
}
return nil
}
func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
db := database.GetDB()
var traffics []*model.OutboundTraffics
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
if err != nil {
logger.Warning(err)
return nil, err
}
return traffics, nil
}
func (s *OutboundService) ResetOutboundTraffic(tag string) error {
db := database.GetDB()
whereText := "tag "
if tag == "-alltags-" {
whereText += " <> ?"
} else {
whereText += " = ?"
}
result := db.Model(model.OutboundTraffics{}).
Where(whereText, tag).
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
err := result.Error
if err != nil {
return err
}
return nil
}

View File

@@ -38,6 +38,7 @@ var defaultValueMap = map[string]string{
"timeLocation": "Asia/Tehran", "timeLocation": "Asia/Tehran",
"tgBotEnable": "false", "tgBotEnable": "false",
"tgBotToken": "", "tgBotToken": "",
"tgBotProxy": "",
"tgBotChatId": "", "tgBotChatId": "",
"tgRunTime": "@daily", "tgRunTime": "@daily",
"tgBotBackup": "false", "tgBotBackup": "false",
@@ -56,6 +57,11 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true", "subEncrypt": "true",
"subShowInfo": "true", "subShowInfo": "true",
"subURI": "", "subURI": "",
"subJsonPath": "/json/",
"subJsonURI": "",
"subJsonFragment": "",
"datepicker": "gregorian",
"warp": "",
} }
type SettingService struct { type SettingService struct {
@@ -244,6 +250,14 @@ func (s *SettingService) SetTgBotToken(token string) error {
return s.setString("tgBotToken", token) return s.setString("tgBotToken", token)
} }
func (s *SettingService) GetTgBotProxy() (string, error) {
return s.getString("tgBotProxy")
}
func (s *SettingService) SetTgBotProxy(token string) error {
return s.setString("tgBotProxy", token)
}
func (s *SettingService) GetTgBotChatId() (string, error) { func (s *SettingService) GetTgBotChatId() (string, error) {
return s.getString("tgBotChatId") return s.getString("tgBotChatId")
} }
@@ -376,17 +390,11 @@ func (s *SettingService) GetSubPort() (int, error) {
} }
func (s *SettingService) GetSubPath() (string, error) { func (s *SettingService) GetSubPath() (string, error) {
subPath, err := s.getString("subPath") return s.getString("subPath")
if err != nil { }
return "", err
} func (s *SettingService) GetSubJsonPath() (string, error) {
if !strings.HasPrefix(subPath, "/") { return s.getString("subJsonPath")
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return subPath, nil
} }
func (s *SettingService) GetSubDomain() (string, error) { func (s *SettingService) GetSubDomain() (string, error) {
@@ -401,8 +409,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
return s.getString("subKeyFile") return s.getString("subKeyFile")
} }
func (s *SettingService) GetSubUpdates() (int, error) { func (s *SettingService) GetSubUpdates() (string, error) {
return s.getInt("subUpdates") return s.getString("subUpdates")
} }
func (s *SettingService) GetSubEncrypt() (bool, error) { func (s *SettingService) GetSubEncrypt() (bool, error) {
@@ -413,12 +421,31 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
return s.getBool("subShowInfo") return s.getBool("subShowInfo")
} }
func (s *SettingService) GetPageSize() (int, error) {
return s.getInt("pageSize")
}
func (s *SettingService) GetSubURI() (string, error) { func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI") return s.getString("subURI")
} }
func (s *SettingService) GetPageSize() (int, error) { func (s *SettingService) GetSubJsonURI() (string, error) {
return s.getInt("pageSize") return s.getString("subJsonURI")
}
func (s *SettingService) GetSubJsonFragment() (string, error) {
return s.getString("subJsonFragment")
}
func (s *SettingService) GetDatepicker() (string, error) {
return s.getString("datepicker")
}
func (s *SettingService) GetWarp() (string, error) {
return s.getString("warp")
}
func (s *SettingService) SetWarp(data string) error {
return s.setString("warp", data)
} }
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
@@ -462,7 +489,9 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() }, "tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() }, "subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
} }
result := make(map[string]interface{}) result := make(map[string]interface{})
@@ -475,10 +504,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
result[key] = value result[key] = value
} }
if result["subEnable"].(bool) && result["subURI"].(string) == "" { if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
subURI := "" subURI := ""
subPort, _ := s.GetSubPort() subPort, _ := s.GetSubPort()
subPath, _ := s.GetSubPath() subPath, _ := s.GetSubPath()
subJsonPath, _ := s.GetSubJsonPath()
subDomain, _ := s.GetSubDomain() subDomain, _ := s.GetSubDomain()
subKeyFile, _ := s.GetSubKeyFile() subKeyFile, _ := s.GetSubKeyFile()
subCertFile, _ := s.GetSubCertFile() subCertFile, _ := s.GetSubCertFile()
@@ -499,12 +529,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
} else { } else {
subURI += fmt.Sprintf("%s:%d", subDomain, subPort) subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
} }
if subPath[0] == byte('/') { if result["subURI"].(string) == "" {
subURI += subPath result["subURI"] = subURI + subPath
} else { }
subURI += "/" + subPath if result["subJsonURI"].(string) == "" {
result["subJsonURI"] = subURI + subJsonPath
} }
result["subURI"] = subURI
} }
return result, nil return result, nil

File diff suppressed because it is too large Load Diff

View File

@@ -95,7 +95,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
if !clientTraffic.Enable { if !clientTraffic.Enable {
clients = RemoveIndex(clients, index-indexDecrease) clients = RemoveIndex(clients, index-indexDecrease)
indexDecrease++ indexDecrease++
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit") logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
} }
@@ -185,7 +185,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
return err return err
} }
if p != nil && p.IsRunning() { if s.IsXrayRunning() {
if !isForce && p.GetConfig().Equals(xrayConfig) { if !isForce && p.GetConfig().Equals(xrayConfig) {
logger.Debug("It does not need to restart xray") logger.Debug("It does not need to restart xray")
return nil return nil

View File

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

View File

@@ -1,6 +1,6 @@
"username" = "Username" "username" = "Username"
"password" = "Password" "password" = "Password"
"login" = "Login" "login" = "Log In"
"confirm" = "Confirm" "confirm" = "Confirm"
"cancel" = "Cancel" "cancel" = "Cancel"
"close" = "Close" "close" = "Close"
@@ -8,7 +8,7 @@
"copied" = "Copied" "copied" = "Copied"
"download" = "Download" "download" = "Download"
"remark" = "Remark" "remark" = "Remark"
"enable" = "Enable" "enable" = "Enabled"
"protocol" = "Protocol" "protocol" = "Protocol"
"search" = "Search" "search" = "Search"
"filter" = "Filter" "filter" = "Filter"
@@ -26,13 +26,13 @@
"edit" = "Edit" "edit" = "Edit"
"delete" = "Delete" "delete" = "Delete"
"reset" = "Reset" "reset" = "Reset"
"copySuccess" = "Copied Successfully" "copySuccess" = "Copied Successful"
"sure" = "Sure" "sure" = "Sure"
"encryption" = "Encryption" "encryption" = "Encryption"
"transmission" = "Transmission" "transmission" = "Transmission"
"host" = "Host" "host" = "Host"
"path" = "Path" "path" = "Path"
"camouflage" = "Camouflage" "camouflage" = "Obfuscation"
"status" = "Status" "status" = "Status"
"enabled" = "Enabled" "enabled" = "Enabled"
"disabled" = "Disabled" "disabled" = "Disabled"
@@ -41,10 +41,10 @@
"offline" = "Offline" "offline" = "Offline"
"online" = "Online" "online" = "Online"
"domainName" = "Domain Name" "domainName" = "Domain Name"
"monitor" = "Listening IP" "monitor" = "Listen IP"
"certificate" = "Certificate" "certificate" = "Certificate"
"fail" = "Fail" "fail" = " Failed"
"success" = "Success" "success" = " Successful"
"getVersion" = "Get Version" "getVersion" = "Get Version"
"install" = "Install" "install" = "Install"
"clients" = "Clients" "clients" = "Clients"
@@ -52,71 +52,74 @@
"secretToken" = "Secret Token" "secretToken" = "Secret Token"
"remained" = "Remained" "remained" = "Remained"
"security" = "Security" "security" = "Security"
"secAlertTitle" = "Security Alert"
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
"secAlertConf" = "Certain configurations have been identified as susceptible to attacks, prompting immediate action to reinforce security protocols and safeguard against potential security breaches."
[menu] [menu]
"dashboard" = "System Status" "dashboard" = "Overview"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"settings" = "Panel Settings" "settings" = "Panel Settings"
"xray" = "Xray Settings" "xray" = "Xray Configs"
"logout" = "Logout" "logout" = "Log Out"
"link" = "Management" "link" = "Manage"
[pages.login] [pages.login]
"title" = "Login" "title" = "Welcome"
"loginAgain" = "Your session has expired, please log in again" "loginAgain" = "Your session has expired, please log in again"
[pages.login.toasts] [pages.login.toasts]
"invalidFormData" = "Input data format is invalid." "invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Please enter username." "emptyUsername" = "Username is required"
"emptyPassword" = "Please enter password." "emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password." "wrongUsernameOrPassword" = "Invalid username or password."
"successLogin" = "Login" "successLogin" = "Login"
[pages.index] [pages.index]
"title" = "System Status" "title" = "Overview"
"memory" = "RAM" "memory" = "RAM"
"hard" = "Disk" "hard" = "Disk"
"xrayStatus" = "Status" "xrayStatus" = "Xray"
"stopXray" = "Stop" "stopXray" = "Stop"
"restartXray" = "Restart" "restartXray" = "Restart"
"xraySwitch" = "SwitchV" "xraySwitch" = "Version"
"xraySwitchClick" = "Choose the version you want to switch to." "xraySwitchClick" = "Choose the version you want to switch to."
"xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations." "xraySwitchClickDesk" = "Choose carefully, as older versions may not be compatible with current configurations."
"operationHours" = "Uptime" "operationHours" = "Uptime"
"systemLoad" = "System Load" "systemLoad" = "System Load"
"systemLoadDesc" = "system load average for the past 1, 5, and 15 minutes" "systemLoadDesc" = "System load average for the past 1, 5, and 15 minutes"
"connectionTcpCountDesc" = "Total TCP connections across all network cards" "connectionTcpCountDesc" = "Total TCP connections across the system"
"connectionUdpCountDesc" = "Total UDP connections across all network cards" "connectionUdpCountDesc" = "Total UDP connections across the system"
"connectionCount" = "Number of Connections" "connectionCount" = "Connection Stats"
"upSpeed" = "Total upload speed for all network cards" "upSpeed" = "Overall upload speed across the system"
"downSpeed" = "Total download speed for all network cards" "downSpeed" = "Overall download speed across the system"
"totalSent" = "Total upload data across all network cards since OS startup" "totalSent" = "Total data sent across the system since OS startup"
"totalReceive" = "Total download data across all network cards since OS startup" "totalReceive" = "Total data received across the system since OS startup"
"xraySwitchVersionDialog" = "Switch Xray Version" "xraySwitchVersionDialog" = "Change Xray Version"
"xraySwitchVersionDialogDesc" = "Are you sure you want to switch the Xray version to" "xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to"
"dontRefresh" = "Installation is in progress, please do not refresh this page" "dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs" "logs" = "Logs"
"config" = "Config" "config" = "Config"
"backup" = "Backup & Restore" "backup" = "Backup & Restore"
"backupTitle" = "Backup & Restore Database" "backupTitle" = "Database Backup & Restore"
"backupDescription" = "It is recommended to backup before importing a new database." "backupDescription" = "It is recommended to make a backup before restoring a database."
"exportDatabase" = "Download Database" "exportDatabase" = "Back Up"
"importDatabase" = "Upload Database" "importDatabase" = "Restore"
[pages.inbounds] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
"totalDownUp" = "Total Uploads/Downloads" "totalDownUp" = "Total Sent/Received"
"totalUsage" = "Total Usage" "totalUsage" = "Total Usage"
"inboundCount" = "Number of Inbounds" "inboundCount" = "Total Inbounds"
"operate" = "Menu" "operate" = "Menu"
"enable" = "Enable" "enable" = "Enabled"
"remark" = "Remark" "remark" = "Remark"
"protocol" = "Protocol" "protocol" = "Protocol"
"port" = "Port" "port" = "Port"
"traffic" = "Traffic" "traffic" = "Traffic"
"details" = "Details" "details" = "Details"
"transportConfig" = "Transport" "transportConfig" = "Transport"
"expireDate" = "Expire Date" "expireDate" = "Duration"
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"generalActions" = "General Actions" "generalActions" = "General Actions"
@@ -124,20 +127,20 @@
"update" = "Update" "update" = "Update"
"modifyInbound" = "Modify Inbound" "modifyInbound" = "Modify Inbound"
"deleteInbound" = "Delete Inbound" "deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Confirm deletion of inbound?" "deleteInboundContent" = "Are you sure you want to delete inbound?"
"deleteClient" = "Delete Client" "deleteClient" = "Delete Client"
"deleteClientContent" = "Are you sure you want to delete client?" "deleteClientContent" = "Are you sure you want to delete client?"
"resetTrafficContent" = "Confirm traffic reset?" "resetTrafficContent" = "Are you sure you want to reset traffic?"
"copyLink" = "Copy Link" "copyLink" = "Copy URL"
"address" = "Address" "address" = "Address"
"network" = "Network" "network" = "Network"
"destinationPort" = "Destination Port" "destinationPort" = "Destination Port"
"targetAddress" = "Target Address" "targetAddress" = "Target Address"
"monitorDesc" = "Leave blank by default" "monitorDesc" = "Leave blank to listen on all IPs"
"meansNoLimit" = "Means no limit" "meansNoLimit" = " = Unlimited. (unit: GB)"
"totalFlow" = "Total Flow" "totalFlow" = "Total Flow"
"leaveBlankToNeverExpire" = "Leave Blank to Never Expire" "leaveBlankToNeverExpire" = "Leave blank to never expire"
"noRecommendKeepDefault" = "No special requirements to maintain default settings" "noRecommendKeepDefault" = "It is recommended to keep the default"
"certificatePath" = "File Path" "certificatePath" = "File Path"
"certificateContent" = "File Content" "certificateContent" = "File Content"
"publicKeyPath" = "Public Key Path" "publicKeyPath" = "Public Key Path"
@@ -146,39 +149,39 @@
"keyContent" = "Private Key Content" "keyContent" = "Private Key Content"
"clickOnQRcode" = "Click on QR Code to Copy" "clickOnQRcode" = "Click on QR Code to Copy"
"client" = "Client" "client" = "Client"
"export" = "Export Links" "export" = "Export All URLs"
"clone" = "Clone" "clone" = "Clone"
"cloneInbound" = "Clone" "cloneInbound" = "Clone"
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone." "cloneInboundContent" = "All settings of this inbound, except Port, Listening IP, and Clients, will be applied to the clone."
"cloneInboundOk" = "Clone" "cloneInboundOk" = "Clone"
"resetAllTraffic" = "Reset All Inbounds Traffic" "resetAllTraffic" = "Reset All Inbounds Traffic"
"resetAllTrafficTitle" = "Reset All Inbounds Traffic" "resetAllTrafficTitle" = "Reset All Inbounds Traffic"
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?" "resetAllTrafficContent" = "Are you sure you want to reset the traffic of all inbounds?"
"resetInboundClientTraffics" = "Reset Clients Traffic" "resetInboundClientTraffics" = "Reset Clients Traffic"
"resetInboundClientTrafficTitle" = "Reset Clients Traffic" "resetInboundClientTrafficTitle" = "Reset Clients Traffic"
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?" "resetInboundClientTrafficContent" = "Are you sure you want to reset the traffic of this inbound's clients?"
"resetAllClientTraffics" = "Reset All Clients Traffic" "resetAllClientTraffics" = "Reset All Clients Traffic"
"resetAllClientTrafficTitle" = "Reset all clients traffic" "resetAllClientTrafficTitle" = "Reset All Clients Traffic"
"resetAllClientTrafficContent" = "Are you sure you want to reset all traffics for all clients?" "resetAllClientTrafficContent" = "Are you sure you want to reset the traffic of all clients?"
"delDepletedClients" = "Delete Depleted Clients" "delDepletedClients" = "Delete Depleted Clients"
"delDepletedClientsTitle" = "Delete Depleted Clients" "delDepletedClientsTitle" = "Delete Depleted Clients"
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?" "delDepletedClientsContent" = "Are you sure you want to delete all the depleted clients?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Please provide a unique email address." "emailDesc" = "Please provide a unique email address."
"IPLimit" = "IP Limit" "IPLimit" = "IP Limit"
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)." "IPLimitDesc" = "Disables inbound if the count exceeds the set value. (0 = disable)"
"IPLimitlog" = "IP Log" "IPLimitlog" = "IP Log"
"IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)." "IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)"
"IPLimitlogclear" = "Clear The Log" "IPLimitlogclear" = "Clear The Log"
"setDefaultCert" = "Set Cert from Panel" "setDefaultCert" = "Set Cert from Panel"
"xtlsDesc" = "Xray core needs to be 1.7.5" "xtlsDesc" = "Xray must be v1.7.5"
"realityDesc" = "Xray core needs to be 1.8.0 or higher." "realityDesc" = "Xray must be v1.8.0+"
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )" "telegramDesc" = "Please provide Telegram or chat ID(s) without using the '@'. (get it here @userinfobot) or (use '/id' command in the bot)"
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations" "subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients."
"info" = "Info" "info" = "Info"
"same" = "Same" "same" = "Same"
"inboundData" = "Inbound's Data" "inboundData" = "Inbound's Data"
"copyToClipboard" = "Copy to Clipboard" "exportInbound" = "Export Inbound"
"import" = "Import" "import" = "Import"
"importInbound" = "Import an Inbound" "importInbound" = "Import an Inbound"
@@ -194,196 +197,221 @@
"last" = "Last" "last" = "Last"
"prefix" = "Prefix" "prefix" = "Prefix"
"postfix" = "Postfix" "postfix" = "Postfix"
"delayedStart" = "Start After First Use" "delayedStart" = "Start on Initial Use"
"expireDays" = "Expire Days" "expireDays" = "Duration"
"days" = "Day(s)" "days" = "Day(s)"
"renew" = "Auto Renew" "renew" = "Auto Renew"
"renewDesc" = "Auto renew days after expiration. 0 = disable" "renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Obtain" "obtain" = "Obtain"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "Request Header" "request" = "Request"
"response" = "Response"
"name" = "Name" "name" = "Name"
"value" = "Value" "value" = "Value"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "Request Version" "version" = "Version"
"requestMethod" = "Request Method" "method" = "Method"
"requestPath" = "Request Path" "path" = "Path"
"responseVersion" = "Response Version" "status" = "Status"
"responseStatus" = "Response Status" "statusDescription" = "Status Desc"
"responseStatusDescription" = "Response Status Description" "requestHeader" = "Request Header"
"responseHeader" = "Response Header" "responseHeader" = "Response Header"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "Encryption" "encryption" = "Encryption"
[pages.settings] [pages.settings]
"title" = "Settings" "title" = "Panel Settings"
"save" = "Save" "save" = "Save"
"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes." "infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes."
"restartPanel" = "Restart Panel" "restartPanel" = "Restart Panel"
"restartPanelDesc" = "Are you sure you want to restart the panel? click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log info on the server." "restartPanelDesc" = "Are you sure you want to restart the panel? If you cannot access the panel after restarting, please view the panel log info on the server."
"actions" = "Actions" "actions" = "Actions"
"resetDefaultConfig" = "Reset to Default Configuration" "resetDefaultConfig" = "Reset to Default"
"panelSettings" = "Panel Settings" "panelSettings" = "General"
"securitySettings" = "Security Settings" "securitySettings" = "Authentication"
"TGBotSettings" = "Telegram Bot Settings" "TGBotSettings" = "Telegram Bot"
"panelListeningIP" = "Panel Listening IP" "panelListeningIP" = "Listen IP"
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs." "panelListeningIPDesc" = "The IP address for the web panel. (leave blank to listen on all IPs)"
"panelListeningDomain" = "Panel Listening Domain" "panelListeningDomain" = "Listen Domain"
"panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs." "panelListeningDomainDesc" = "The domain name for the web panel. (leave blank to listen on all domains and IPs)"
"panelPort" = "Panel Port" "panelPort" = "Listen Port"
"panelPortDesc" = "Port number for serving the panel." "panelPortDesc" = "The port number for the web panel. (must be an unused port)"
"publicKeyPath" = "Panel Certificate Public Key Path" "publicKeyPath" = "Public Key Path"
"publicKeyPathDesc" = "Fill in an absolute path starting with." "publicKeyPathDesc" = "The public key file path for the web panel. (begins with /)"
"privateKeyPath" = "Panel Certificate Private Key Path" "privateKeyPath" = "Private Key Path"
"privateKeyPathDesc" = "Fill in an absolute path starting with." "privateKeyPathDesc" = "The private key file path for the web panel. (begins with /)"
"panelUrlPath" = "Panel URL Root Path" "panelUrlPath" = "URI Path"
"panelUrlPathDesc" = "Must start with '/' and end with." "panelUrlPathDesc" = "The URI path for the web panel. (begins with / and concludes with /)"
"pageSize" = "Pagination Size" "pageSize" = "Pagination Size"
"pageSizeDesc" = "Define page size for inbounds table. Set 0 to disable" "pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
"remarkModel" = "Remark Model and Seperation Charachter" "remarkModel" = "Remark Model & Separation Character"
"datepicker" = "Calendar Type"
"datepickerPlaceholder" = "Select date"
"datepickerDescription" = "Scheduled tasks will run based on this calendar."
"sampleRemark" = "Sample Remark" "sampleRemark" = "Sample Remark"
"oldUsername" = "Current Username" "oldUsername" = "Current Username"
"currentPassword" = "Current Password" "currentPassword" = "Current Password"
"newUsername" = "New Username" "newUsername" = "New Username"
"newPassword" = "New Password" "newPassword" = "New Password"
"telegramBotEnable" = "Enable Telegram Bot" "telegramBotEnable" = "Enable Telegram Bot"
"telegramBotEnableDesc" = "Connect to the features of this panel through the Telegram bot." "telegramBotEnableDesc" = "Enables the Telegram bot."
"telegramToken" = "Telegram Token" "telegramToken" = "Telegram Token"
"telegramTokenDesc" = "The token you have got from @BotFather." "telegramTokenDesc" = "The Telegram bot token obtained from '@BotFather'."
"telegramChatId" = "Telegram Admin Chat IDs" "telegramProxy" = "SOCKS Proxy"
"telegramChatIdDesc" = "Multiple chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs." "telegramProxyDesc" = "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)"
"telegramNotifyTime" = "Telegram bot notification time" "telegramChatId" = "Admin Chat ID"
"telegramNotifyTimeDesc" = "Use crontab timing format." "telegramChatIdDesc" = "The Telegram Admin Chat ID(s). (comma-separated)(get it here @userinfobot) or (use '/id' command in the bot)"
"telegramNotifyTime" = "Notification Time"
"telegramNotifyTimeDesc" = "The Telegram bot notification time set for periodic reports. (use the crontab time format)"
"tgNotifyBackup" = "Database Backup" "tgNotifyBackup" = "Database Backup"
"tgNotifyBackupDesc" = "Include database backup file with report notification." "tgNotifyBackupDesc" = "Send a database backup file with a report."
"tgNotifyLogin" = "Login Notification" "tgNotifyLogin" = "Login Notification"
"tgNotifyLoginDesc" = "Displays the username, IP address, and time when someone tries to log into your panel." "tgNotifyLoginDesc" = "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel."
"sessionMaxAge" = "Session Duration" "sessionMaxAge" = "Session Duration"
"sessionMaxAgeDesc" = "The duration of a login session. (unit: minute)" "sessionMaxAgeDesc" = "The duration for which you can stay logged in. (unit: minute)"
"expireTimeDiff" = "Client Expiration Threshold Notification" "expireTimeDiff" = "Expiration Date Notification"
"expireTimeDiffDesc" = "Get notified about client expiration before the threshold. (unit: day)" "expireTimeDiffDesc" = "Get notified about expiration date when reaching this threshold. (unit: day)"
"trafficDiff" = "Traffic Limit Threshold Notification" "trafficDiff" = "Traffic Cap Notification"
"trafficDiffDesc" = "Get notified about traffic exhaustion before reaching the threshold. (unit: GB)" "trafficDiffDesc" = "Get notified about traffic cap when reaching this threshold. (unit: GB)"
"tgNotifyCpu" = "CPU Load Threshold Notification" "tgNotifyCpu" = "CPU Load Notification"
"tgNotifyCpuDesc" = "Get notified if CPU usage exceeds this threshold. (unit: %)" "tgNotifyCpuDesc" = "Get notified if CPU load exceeds this threshold. (unit: %)"
"timeZone" = "Time zone" "timeZone" = "Time Zone"
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone." "timeZoneDesc" = "Scheduled tasks will run based on this time zone."
"subSettings" = "Subscription" "subSettings" = "Subscription"
"subEnable" = "Enable Service" "subEnable" = "Enable Subscription Service"
"subEnableDesc" = "Subscription feature with separate configuration." "subEnableDesc" = "Enables the subscription service."
"subListen" = "Listening IP" "subListen" = "Listen IP"
"subListenDesc" = "Leave blank by default to monitor all IPs." "subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
"subPort" = "Subscription Port" "subPort" = "Listen Port"
"subPortDesc" = "Port number for serving the subscription service. Must be unused in server." "subPortDesc" = "The port number for the subscription service. (must be an unused port)"
"subCertPath" = "Subscription Certificate Public Key Path" "subCertPath" = "Public Key Path"
"subCertPathDesc" = "Fill in an absolute path starting with '/'" "subCertPathDesc" = "The public key file path for the subscription service. (begins with /)"
"subKeyPath" = "Subscription Certificate Private Key Path" "subKeyPath" = "Private Key Path"
"subKeyPathDesc" = "Fill in an absolute path starting with '/'" "subKeyPathDesc" = "The private key file path for the subscription service. (begins with /)"
"subPath" = "Subscription URL Root Path" "subPath" = "URI Path"
"subPathDesc" = "Must start with '/' and end with '/'" "subPathDesc" = "The URI path for the subscription service. (begins with / and concludes with /)"
"subDomain" = "Listening Domain" "subDomain" = "Listen Domain"
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs." "subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
"subUpdates" = "Subscription update intervals" "subUpdates" = "Update Intervals"
"subUpdatesDesc" = "Interval hours between updates in client application." "subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
"subEncrypt" = "Encode Configs" "subEncrypt" = "Encode"
"subEncryptDesc" = "Encode the returned configs in subscription." "subEncryptDesc" = "The returned content of subscription service will be Base64 encoded."
"subShowInfo" = "Show Usage Info" "subShowInfo" = "Show Usage Info"
"subShowInfoDesc" = "Show remained traffic and date after config name." "subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI" "subURI" = "Reverse Proxy URI"
"subURIDesc" = "Change base URI of subscription URL for using on behind of proxies." "subURIDesc" = "The URI path of the subscription URL for use behind proxies."
"fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
[pages.xray] [pages.xray]
"title" = "Xray Settings" "title" = "Xray Configs"
"save" = "Save Settings" "save" = "Save"
"restart" = "Restart Xray" "restart" = "Restart Xray"
"basicTemplate" = "Basic Template" "basicTemplate" = "Basics"
"advancedTemplate" = "Advanced Template" "advancedTemplate" = "Advanced"
"generalConfigs" = "General Configs" "generalConfigs" = "General"
"generalConfigsDesc" = "These options will provide general adjustments." "generalConfigsDesc" = "These options will determine general adjustments."
"blockConfigs" = "Blocking Configs" "logConfigs" = "Log"
"blockConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites." "logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs"
"blockCountryConfigs" = "Block Country Configs" "blockConfigs" = "Protection Shield"
"blockCountryConfigsDesc" = "These options will prevent users from connecting to specific country domains." "blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
"directCountryConfigs" = "Direct Country Configs" "blockCountryConfigs" = "Block Country"
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains." "blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
"ipv4Configs" = "IPv4 Configs" "directCountryConfigs" = "Direct Country"
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4." "directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
"warpConfigs" = "WARP Configs" "ipv4Configs" = "IPv4 Routing"
"warpConfigsDesc" = "Caution: Before using these options, install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers." "ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
"Template" = "Xray Configuration Template" "warpConfigs" = "WARP Routing"
"TemplateDesc" = "Generate the final Xray configuration file based on this template." "warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP."
"FreedomStrategy" = "Configure Strategy for Freedom Protocol" "Template" = "Advanced Xray Configuration Template"
"FreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol." "TemplateDesc" = "The final Xray config file will be generated based on this template."
"RoutingStrategy" = "Configure Domains Routing Strategy" "FreedomStrategy" = "Freedom Protocol Strategy"
"RoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving." "FreedomStrategyDesc" = "Set the output strategy for the network in the Freedom Protocol."
"Torrent" = "Ban BitTorrent Protocol" "RoutingStrategy" = "Overall Routing Strategy"
"TorrentDesc" = "Change the configuration template to avoid using BitTorrent protocol." "RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
"PrivateIp" = "Ban Private IPs to Connect" "Torrent" = "Block BitTorrent Protocol"
"PrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges." "TorrentDesc" = "Blocks BitTorrent protocol."
"PrivateIp" = "Block Connection to Private IPs"
"PrivateIpDesc" = "Blocks establishing connections to private IP ranges."
"Ads" = "Block Ads" "Ads" = "Block Ads"
"AdsDesc" = "Change the configuration template to block ads." "AdsDesc" = "Blocks advertising websites."
"Family" = "Block Malware and Adult Content" "Family" = "Family Protection"
"FamilyDesc" = "DNS resolvers to block malware and adult content for family protection." "FamilyDesc" = "Blocks adult content, and malware websites."
"Speedtest" = "Block Speedtest Websites" "Security" = "Security Shield"
"SpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites." "SecurityDesc" = "Blocks malware, phishing, and cryptominers websites."
"IRIp" = "Disable Connection to Iran IPs" "Speedtest" = "Block Speedtest"
"IRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges." "SpeedtestDesc" = "Blocks establishing connectins to speedtest websites."
"IRDomain" = "Disable Connection to Iran Domains" "IRIp" = "Block Connection to Iran IPs"
"IRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains." "IRIpDesc" = "Blocks establishing connections to Iran IP ranges."
"ChinaIp" = "Disable Connection to China IPs" "IRDomain" = "Block Connection to Iran Domains"
"ChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges." "IRDomainDesc" = "Blocks establishing connections to Iran domains."
"ChinaDomain" = "Disable Connection to China Domains" "ChinaIp" = "Block Connection to China IPs"
"ChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains." "ChinaIpDesc" = "Blocks establishing connections to China IP ranges."
"RussiaIp" = "Disable Connection to Russia IPs" "ChinaDomain" = "Block Connection to China Domains"
"RussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges." "ChinaDomainDesc" = "Blocks establishing connections to China domains."
"RussiaDomain" = "Disable Connection to Russia Domains" "RussiaIp" = "Block Connection to Russia IPs"
"RussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains." "RussiaIpDesc" = "Blocks establishing connections to Russia IP ranges."
"VNIp" = "Disable Connection to Vietnam IPs" "RussiaDomain" = "Block Connection to Russia Domains"
"VNIpDesc" = "Change the configuration template to avoid connecting to Vietnam IP ranges." "RussiaDomainDesc" = "Blocks establishing connections to Russia domains."
"VNDomain" = "Disable Connection to Vietnam Domains" "VNIp" = "Block Connection to Vietnam IPs"
"VNDomainDesc" = "Change the configuration template to avoid connecting to Vietnam domains." "VNIpDesc" = "Blocks establishing connections to Vietnam IP ranges."
"VNDomain" = "Block Connection to Vietnam Domains"
"VNDomainDesc" = "Blocks establishing connections to Vietnam domains."
"DirectIRIp" = "Direct Connection to Iran IPs" "DirectIRIp" = "Direct Connection to Iran IPs"
"DirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges." "DirectIRIpDesc" = "Directly establishes connections to Iran IP ranges."
"DirectIRDomain" = "Direct Connection to Iran Domains" "DirectIRDomain" = "Direct Connection to Iran Domains"
"DirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains." "DirectIRDomainDesc" = "Directly establishes connections to Iran domains."
"DirectChinaIp" = "Direct Connection to China IPs" "DirectChinaIp" = "Direct Connection to China IPs"
"DirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges." "DirectChinaIpDesc" = "Directly establishes connections to China IP ranges."
"DirectChinaDomain" = "Direct Connection to China Domains" "DirectChinaDomain" = "Direct Connection to China Domains"
"DirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains." "DirectChinaDomainDesc" = "Directly establishes connections to China domains."
"DirectRussiaIp" = "Direct Connection to Russia IPs" "DirectRussiaIp" = "Direct Connection to Russia IPs"
"DirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges." "DirectRussiaIpDesc" = "Directly establishes connections to Russia IP ranges."
"DirectRussiaDomain" = "Direct Connection to Russia Domains" "DirectRussiaDomain" = "Direct Connection to Russia Domains"
"DirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains." "DirectRussiaDomainDesc" = "Directly establishes connections to Russia domains."
"DirectVNIp" = "Direct Connection to Vietnam IPs" "DirectVNIp" = "Direct Connection to Vietnam IPs"
"DirectVNIpDesc" = "Change the configuration template for direct connecting to Vietnam IP ranges." "DirectVNIpDesc" = "Directly establishes connections to Vietnam IP ranges."
"DirectVNDomain" = "Direct Connection to Vietnam Domains" "DirectVNDomain" = "Direct Connection to Vietnam Domains"
"DirectVNDomainDesc" = "Change the configuration template for direct connecting to Vietnam domains." "DirectVNDomainDesc" = "Directly establishes connections to Vietnam domains."
"GoogleIPv4" = "Use IPv4 for Google" "GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "Add routing for Google to connect with IPv4." "GoogleIPv4Desc" = "Routes traffic to Google via IPv4."
"NetflixIPv4" = "Use IPv4 for Netflix" "NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4." "NetflixIPv4Desc" = "Routes traffic to Netflix via IPv4."
"GoogleWARP" = "Route Google through WARP." "GoogleWARP" = "Google"
"GoogleWARPDesc" = "Add routing for Google via WARP." "GoogleWARPDesc" = "Add routing for Google via WARP."
"OpenAIWARP" = "Route OpenAI (ChatGPT) through WARP." "OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP." "OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
"NetflixWARP" = "Route Netflix through WARP." "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Add routing for Netflix via WARP." "NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
"SpotifyWARP" = "Route Spotify through WARP." "MetaWARP" = "Meta"
"SpotifyWARPDesc" = "Add routing for Spotify via WARP." "MetaWARPDesc" = "Routes traffic to Meta (Instagram, Facebook, WhatsApp, Threads,...) via WARP."
"IRWARP" = "Route Iran domains through WARP." "AppleWARP" = "Apple"
"IRWARPDesc" = "Add routing for Iran domains via WARP." "AppleWARPDesc" = "Routes traffic to Apple via WARP."
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "Routes traffic to Reddit via WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
"IRWARP" = "Iran domains"
"IRWARPDesc" = "Routes traffic to Iran domains via WARP."
"Inbounds" = "Inbounds" "Inbounds" = "Inbounds"
"InboundsDesc" = "Change the configuration template to accept specific clients." "InboundsDesc" = "Accepting the specific clients."
"Outbounds" = "Outbounds" "Outbounds" = "Outbounds"
"OutboundsDesc" = "Change the configuration template to define outgoing ways for this server." "Balancers" = "Balancers"
"OutboundsDesc" = "Set the outgoing traffic pathway."
"Routings" = "Routing Rules" "Routings" = "Routing Rules"
"RoutingsDesc" = "The priority of each rule is important!" "RoutingsDesc" = "The priority of each rule is important!"
"completeTemplate" = "All" "completeTemplate" = "All"
"logLevel" = "Log Level"
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
"accessLog" = "Access Log"
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
"errorLog" = "Error Log"
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
[pages.xray.rules] [pages.xray.rules]
"first" = "First" "first" = "First"
@@ -394,10 +422,11 @@
"dest" = "Destination" "dest" = "Destination"
"inbound" = "Inbound" "inbound" = "Inbound"
"outbound" = "Outbound" "outbound" = "Outbound"
"balancer" = "Balancer"
"info" = "Info" "info" = "Info"
"add" = "Add Rule" "add" = "Add Rule"
"edit" = "Edit Rule" "edit" = "Edit Rule"
"useComma" = "Comma separated items" "useComma" = "Comma-separated items"
[pages.xray.outbound] [pages.xray.outbound]
"addOutbound" = "Add Outbound" "addOutbound" = "Add Outbound"
@@ -414,20 +443,52 @@
"portal" = "Portal" "portal" = "Portal"
"intercon" = "Interconnection" "intercon" = "Interconnection"
[pages.xray.balancer]
"addBalancer" = "Add Balancer"
"editBalancer" = "Edit Balancer"
"balancerStrategy" = "Strategy"
"balancerSelectors" = "Selectors"
"tag" = "Tag"
"tagDesc" = "Unique Tag"
"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work."
[pages.xray.wireguard]
"secretKey" = "Secret Key"
"publicKey" = "Public Key"
"allowedIPs" = "Allowed IPs"
"endpoint" = "Endpoint"
"psk" = "PreShared Key"
"domainStrategy" = "Domain Strategy"
[pages.xray.dns]
"enable" = "Enable DNS"
"enableDesc" = "Enable built-in DNS server"
"strategy" = "Query Strategy"
"strategyDesc" = "Overall strategy to resolve domain names"
"add" = "Add Server"
"edit" = "Edit Server"
"domains" = "Domains"
[pages.xray.fakedns]
"add" = "Add Fake DNS"
"edit" = "Edit Fake DNS"
"ipPool" = "IP Pool Subnet"
"poolSize" = "Pool Size"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Admin"
"secret" = "Secret Token" "secret" = "Secret Token"
"loginSecurity" = "Login security" "loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Enable additional user login security step" "loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
"secretToken" = "Secret Token" "secretToken" = "Secret Token"
"secretTokenDesc" = "Please copy and securely store this token in a safe place. This token is required for login and cannot be recovered from the x-ui command tool." "secretTokenDesc" = "Please securely store this token in a safe place. This token is required for login and cannot be recovered."
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Modify Settings" "modifySettings" = "Modify Settings"
"getSettings" = "Get Settings" "getSettings" = "Get Settings"
"modifyUser" = "Modify User" "modifyUser" = "Modify Admin"
"originalUserPassIncorrect" = "Incorrect original username or password" "originalUserPassIncorrect" = "The Current username or password is invalid"
"userPassMustBeNotEmpty" = "New username and password cannot be empty" "userPassMustBeNotEmpty" = "The new username and password is empty"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Custom keyboard closed!" "keyboardClosed" = "❌ Custom keyboard closed!"
@@ -436,7 +497,8 @@
"wentWrong" = "❌ Something went wrong!" "wentWrong" = "❌ Something went wrong!"
"noIpRecord" = "❗ No IP Record!" "noIpRecord" = "❗ No IP Record!"
"noInbounds" = "❗ No inbound found!" "noInbounds" = "❗ No inbound found!"
"unlimited" = "♾ Unlimited" "unlimited" = "♾ Unlimited(Reset)"
"add" = "Add"
"month" = "Month" "month" = "Month"
"months" = "Months" "months" = "Months"
"day" = "Day" "day" = "Day"
@@ -445,48 +507,51 @@
"unknown" = "Unknown" "unknown" = "Unknown"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"clients" = "Clients" "clients" = "Clients"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ Unknown command" "unknown" = "❗ Unknown command."
"pleaseChoose" = "👇 Please choose:\r\n" "pleaseChoose" = "👇 Please choose:\r\n"
"help" = "🤖 Welcome to this bot! It's designed to offer you specific data from the server, and it allows you to make modifications as needed.\r\n\r\n" "help" = "🤖 Welcome to this bot! It's designed to offer specific data from the web panel and allows you to make modifications as needed.\r\n\r\n"
"start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n" "start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n"
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n" "welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
"status" = "✅ Bot is OK!" "status" = "✅ Bot is OK!"
"usage" = "❗ Please provide a text to search!" "usage" = "❗ Please provide a text to search!"
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>" "getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>" "helpAdminCommands" = "To 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>"
"helpClientCommands" = "To search for statistics, just use the following command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan." "helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% is more than threshold {{ .Threshold }}%" "cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"
"selectUserFailed" = "❌ Error in user selection!" "selectUserFailed" = "❌ Error in user selection!"
"userSaved" = "✅ Telegram User saved." "userSaved" = "✅ Telegram User saved."
"loginSuccess" = "✅ Successfully logged-in to the panel.\r\n" "loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
"loginFailed" = "❗️ Login to the panel failed.\r\n" "loginFailed" = "❗️ Log in to the panel failed.\r\n"
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n" "report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n" "datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Hostname: {{ .Hostname }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 X-UI Version: {{ .Version }}\r\n" "version" = "🚀 3X-UI Version: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n" "ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Server RAM: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" "tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\r\n" "udpCount" = "🔸 UDP: {{ .Count }}\r\n"
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Xray Status: {{ .State }}\r\n" "xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Username: {{ .Username }}\r\n" "username" = "👤 Username: {{ .Username }}\r\n"
"time" = "⏰ Time: {{ .Time }}\r\n" "time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n"
"expire" = "📅 Expire Date: {{ .Time }}\r\n" "expire" = "📅 Expire Date: {{ .Time }}\r\n"
"expireIn" = "📅 Expire In: {{ .Time }}\r\n" "expireIn" = "📅 Expire In: {{ .Time }}\r\n"
"active" = "💡 Active: ✅ Yes\r\n" "active" = "💡 Active: {{ .Enable }}\r\n"
"inactive" = "💡 Active: ❌ No\r\n" "enabled" = "🚨 Enabled: {{ .Enable }}\r\n"
"online" = "🌐 Connection status: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" "upload" = "🔼 Upload: ↑{{ .Upload }}\r\n"
"download" = "🔽 Download: ↓{{ .Download }}\r\n" "download" = "🔽 Download: ↓{{ .Download }}\r\n"
@@ -494,10 +559,13 @@
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n" "TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n" "exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n" "exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
"onlinesCount" = "🌐 Online Clients: {{ .Count }}\r\n"
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n" "disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n" "backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n"
"yes" = "✅ Yes"
"no" = "❌ No"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Close Keyboard" "closeKeyboard" = "❌ Close Keyboard"
@@ -507,44 +575,47 @@
"confirmResetTraffic" = "✅ Confirm Reset Traffic?" "confirmResetTraffic" = "✅ Confirm Reset Traffic?"
"confirmClearIps" = "✅ Confirm Clear IPs?" "confirmClearIps" = "✅ Confirm Clear IPs?"
"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?" "confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?"
"confirmToggle" = "✅ Confirm Enable/Disable User?"
"dbBackup" = "Get DB Backup" "dbBackup" = "Get DB Backup"
"serverUsage" = "Server Usage" "serverUsage" = "Server Usage"
"getInbounds" = "Get Inbounds" "getInbounds" = "Get Inbounds"
"depleteSoon" = "Deplete soon" "depleteSoon" = "Deplete Soon"
"clientUsage" = "Get Usage" "clientUsage" = "Get Usage"
"onlines" = "Online Clients"
"commands" = "Commands" "commands" = "Commands"
"refresh" = "🔄 Refresh" "refresh" = "🔄 Refresh"
"clearIPs" = "❌ Clear IPs" "clearIPs" = "❌ Clear IPs"
"removeTGUser" = "❌ Remove Telegram User" "removeTGUser" = "❌ Remove Telegram User"
"selectTGUser" = "👤 Select Telegram User" "selectTGUser" = "👤 Select Telegram User"
"selectOneTGUser" = "👤 Select a telegram user:" "selectOneTGUser" = "👤 Select a Telegram User:"
"resetTraffic" = "📈 Reset Traffic" "resetTraffic" = "📈 Reset Traffic"
"resetExpire" = "📅 Reset Expire Days" "resetExpire" = "📅 Change Expiry Date"
"ipLog" = "🔢 IP Log" "ipLog" = "🔢 IP Log"
"ipLimit" = "🔢 IP Limit" "ipLimit" = "🔢 IP Limit"
"setTGUser" = "👤 Set Telegram User" "setTGUser" = "👤 Set Telegram User"
"toggle" = "🔘 Enable / Disable" "toggle" = "🔘 Enable / Disable"
"custom" = "🔢 Custom" "custom" = "🔢 Custom"
"confirmNumber" = "✅ Confirm : {{ .Num }}" "confirmNumber" = "✅ Confirm: {{ .Num }}"
"confirmNumberAdd" = "✅ Confirm adding: {{ .Num }}"
"limitTraffic" = "🚧 Traffic Limit" "limitTraffic" = "🚧 Traffic Limit"
"getBanLogs" = "Get Ban Logs"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Successful!" "successfulOperation" = "✅ Operation successful!"
"errorOperation" = "❗ Error in Operation." "errorOperation" = "❗ Error in operation."
"getInboundsFailed" = "❌ Failed to get inbounds" "getInboundsFailed" = "❌ Failed to get inbounds."
"canceled" = "❌ {{ .Email }} : Operation canceled." "canceled" = "❌ {{ .Email }}: Operation canceled."
"clientRefreshSuccess" = "✅ {{ .Email }} : Client refreshed successfully." "clientRefreshSuccess" = "✅ {{ .Email }}: Client refreshed successfully."
"IpRefreshSuccess" = "✅ {{ .Email }} : IPs refreshed successfully." "IpRefreshSuccess" = "✅ {{ .Email }}: IPs refreshed successfully."
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Client's Telegram User refreshed successfully." "TGIdRefreshSuccess" = "✅ {{ .Email }}: Client's Telegram User refreshed successfully."
"resetTrafficSuccess" = "✅ {{ .Email }} : Traffic reset successfully." "resetTrafficSuccess" = "✅ {{ .Email }}: Traffic reset successfully."
"setTrafficLimitSuccess" = "✅ {{ .Email }} : Traffic limit saved successfully." "setTrafficLimitSuccess" = "✅ {{ .Email }}: Traffic limit saved successfully."
"expireResetSuccess" = "✅ {{ .Email }} : Expire days reset successfully." "expireResetSuccess" = "✅ {{ .Email }}: Expire days reset successfully."
"resetIpSuccess" = "✅ {{ .Email }} : IP limit {{ .Count }} saved successfully." "resetIpSuccess" = "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully."
"clearIpSuccess" = "✅ {{ .Email }} : IPs cleared successfully." "clearIpSuccess" = "✅ {{ .Email }}: IPs cleared successfully."
"getIpLog" = "✅ {{ .Email }} : Get IP Log." "getIpLog" = "✅ {{ .Email }}: Get IP Log."
"getUserInfo" = "✅ {{ .Email }} : Get Telegram User Info." "getUserInfo" = "✅ {{ .Email }}: Get Telegram User Info."
"removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully." "removedTGUserSuccess" = "✅ {{ .Email }}: Telegram User removed successfully."
"enableSuccess" = "✅ {{ .Email }} : Enabled successfully." "enableSuccess" = "✅ {{ .Email }}: Enabled successfully."
"disableSuccess" = "✅ {{ .Email }} : Disabled successfully." "disableSuccess" = "✅ {{ .Email }}: Disabled successfully."
"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>" "askToAddUserId" = "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ID in your configuration(s).\r\n\r\nYour User ID: <code>{{ .TgUserID }}</code>"
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"

View File

@@ -1,6 +1,6 @@
"username" = "Nombre de Usuario" "username" = "Nombre de Usuario"
"password" = "Contraseña" "password" = "Contraseña"
"login" = "Acceso" "login" = "Acceder"
"confirm" = "Confirmar" "confirm" = "Confirmar"
"cancel" = "Cancelar" "cancel" = "Cancelar"
"close" = "Cerrar" "close" = "Cerrar"
@@ -20,7 +20,7 @@
"check" = "Verificar" "check" = "Verificar"
"indefinite" = "Indefinido" "indefinite" = "Indefinido"
"unlimited" = "Ilimitado" "unlimited" = "Ilimitado"
"none" = "Ninguno" "none" = "None"
"qrCode" = "Código QR" "qrCode" = "Código QR"
"info" = "Más Información" "info" = "Más Información"
"edit" = "Editar" "edit" = "Editar"
@@ -52,17 +52,20 @@
"secretToken" = "Token Secreto" "secretToken" = "Token Secreto"
"remained" = "Restante" "remained" = "Restante"
"security" = "Seguridad" "security" = "Seguridad"
"secAlertTitle" = "Alerta de seguridad"
"secAlertSsl" = "Esta conexión no es segura. Evite ingresar información confidencial hasta que TLS esté activado para la protección de datos."
"secAlertConf" = "Se han identificado ciertas configuraciones como susceptibles a ataques, lo que genera acciones inmediatas para reforzar los protocolos de seguridad y proteger contra posibles violaciones de seguridad."
[menu] [menu]
"dashboard" = "Estado del Sistema" "dashboard" = "Estado del Sistema"
"inbounds" = "Entradas" "inbounds" = "Entradas"
"settings" = "Configuraciones" "settings" = "Configuraciones"
"xray" = "Configuración Xray" "xray" = "Ajustes Xray"
"logout" = "Cerrar Sesión" "logout" = "Cerrar Sesión"
"link" = "Otro" "link" = "Gestionar"
[pages.login] [pages.login]
"title" = "Iniciar Sesión" "title" = "Bienvenido"
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente." "loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
[pages.login.toasts] [pages.login.toasts]
@@ -76,10 +79,10 @@
"title" = "Estado del Sistema" "title" = "Estado del Sistema"
"memory" = "Memoria" "memory" = "Memoria"
"hard" = "Disco Duro" "hard" = "Disco Duro"
"xrayStatus" = "Estado de Xray" "xrayStatus" = "Xray"
"stopXray" = "Detener Xray" "stopXray" = "Detener"
"restartXray" = "Reiniciar" "restartXray" = "Reiniciar"
"xraySwitch" = "Cambiar Versión" "xraySwitch" = "Versión"
"xraySwitchClick" = "Elige la versión a la que deseas cambiar." "xraySwitchClick" = "Elige la versión a la que deseas cambiar."
"xraySwitchClickDesk" = "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales." "xraySwitchClickDesk" = "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales."
"operationHours" = "Tiempo de Funcionamiento" "operationHours" = "Tiempo de Funcionamiento"
@@ -134,7 +137,7 @@
"destinationPort" = "Puerto de Destino" "destinationPort" = "Puerto de Destino"
"targetAddress" = "Dirección de Destino" "targetAddress" = "Dirección de Destino"
"monitorDesc" = "Dejar en blanco por defecto" "monitorDesc" = "Dejar en blanco por defecto"
"meansNoLimit" = "Significa Sin Límite" "meansNoLimit" = " = illimitata. (unidad: GB)"
"totalFlow" = "Flujo Total" "totalFlow" = "Flujo Total"
"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar" "leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar"
"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada" "noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada"
@@ -173,12 +176,12 @@
"setDefaultCert" = "Establecer certificado desde el panel" "setDefaultCert" = "Establecer certificado desde el panel"
"xtlsDesc" = "La versión del núcleo de Xray debe ser 1.7.5" "xtlsDesc" = "La versión del núcleo de Xray debe ser 1.7.5"
"realityDesc" = "La versión del núcleo de Xray debe ser 1.8.0 o superior." "realityDesc" = "La versión del núcleo de Xray debe ser 1.8.0 o superior."
"telegramDesc" = "Utiliza el ID de Telegram sin @ o los IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)." "telegramDesc" = "Utiliza únicamente IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)."
"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones." "subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones."
"info" = "Info" "info" = "Info"
"same" = "misma" "same" = "misma"
"inboundData" = "Datos de entrada" "inboundData" = "Datos de entrada"
"copyToClipboard" = "Copiar al portapapeles" "exportInbound" = "Exportación entrante"
"import" = "Importar" "import" = "Importar"
"importInbound" = "Importar un entrante" "importInbound" = "Importar un entrante"
@@ -195,27 +198,28 @@
"prefix" = "Prefijo" "prefix" = "Prefijo"
"postfix" = "Sufijo" "postfix" = "Sufijo"
"delayedStart" = "Iniciar después del primer uso" "delayedStart" = "Iniciar después del primer uso"
"expireDays" = "Días de Expiración" "expireDays" = "Duratio"
"days" = "día(s)" "days" = "día(s)"
"renew" = "Renovación automática" "renew" = "Renovación automática"
"renewDesc" = "Renovación automática días después del vencimiento. 0 = deshabilitar" "renewDesc" = "Auto-renovatio post tutelam receptam. (0 = disable) (unitas: dies)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Recibir" "obtain" = "Recibir"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "Encabezado de la Petición" "request" = "Pedido"
"response" = "Respuesta"
"name" = "Nombre" "name" = "Nombre"
"value" = "Valor" "value" = "Valor"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "Versión de la Petición" "version" = "Versión"
"requestMethod" = "Método de la Petición" "method" = "Método"
"requestPath" = "Ruta de la Petición" "path" = "Camino"
"responseVersion" = "Versión de la Respuesta" "status" = "Estado"
"responseStatus" = "Estado de la Respuesta" "statusDescription" = "Descripción de la Situación"
"responseStatusDescription" = "Descripción del Estado de la Respuesta" "requestHeader" = "Encabezado de solicitud"
"responseHeader" = "Encabezado de la Respuesta" "responseHeader" = "Encabezado de respuesta"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "Cifrado" "encryption" = "Cifrado"
@@ -246,6 +250,9 @@
"pageSize" = "Tamaño de paginación" "pageSize" = "Tamaño de paginación"
"pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar" "pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar"
"remarkModel" = "Modelo de observación y carácter de separación" "remarkModel" = "Modelo de observación y carácter de separación"
"datepicker" = "selector de fechas"
"datepickerPlaceholder" = "Seleccionar fecha"
"datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento"
"sampleRemark" = "Observación de muestra" "sampleRemark" = "Observación de muestra"
"oldUsername" = "Nombre de Usuario Actual" "oldUsername" = "Nombre de Usuario Actual"
"currentPassword" = "Contraseña Actual" "currentPassword" = "Contraseña Actual"
@@ -255,6 +262,8 @@
"telegramBotEnableDesc" = "Conéctese a las funciones de este panel a través del bot de Telegram." "telegramBotEnableDesc" = "Conéctese a las funciones de este panel a través del bot de Telegram."
"telegramToken" = "Token de Telegram" "telegramToken" = "Token de Telegram"
"telegramTokenDesc" = "Debe obtener el token del administrador de bots de Telegram @botfather." "telegramTokenDesc" = "Debe obtener el token del administrador de bots de Telegram @botfather."
"telegramProxy" = "Socks5 Proxy"
"telegramProxyDesc" = "Si necesita el proxy Socks5 para conectarse a Telegram. Ajuste su configuración según la guía."
"telegramChatId" = "IDs de Chat de Telegram para Administradores" "telegramChatId" = "IDs de Chat de Telegram para Administradores"
"telegramChatIdDesc" = "IDs de Chat múltiples separados por comas. Use @userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat." "telegramChatIdDesc" = "IDs de Chat múltiples separados por comas. Use @userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat."
"telegramNotifyTime" = "Hora de Notificación del Bot de Telegram" "telegramNotifyTime" = "Hora de Notificación del Bot de Telegram"
@@ -296,6 +305,8 @@
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." "subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
"subURI" = "URI de proxy inverso" "subURI" = "URI de proxy inverso"
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy" "subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
"fragment" = "Fragmentación"
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo TLS"
[pages.xray] [pages.xray]
"title" = "Xray Configuración" "title" = "Xray Configuración"
@@ -305,6 +316,8 @@
"advancedTemplate" = "Plantilla Avanzada" "advancedTemplate" = "Plantilla Avanzada"
"generalConfigs" = "Configuraciones Generales" "generalConfigs" = "Configuraciones Generales"
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales." "generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
"logConfigs" = "Registro"
"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlo sabiamente solo en caso de sus necesidades."
"blockConfigs" = "Configuraciones de Bloqueo" "blockConfigs" = "Configuraciones de Bloqueo"
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos." "blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
"blockCountryConfigs" = "Configuraciones de Bloqueo por País" "blockCountryConfigs" = "Configuraciones de Bloqueo por País"
@@ -327,8 +340,10 @@
"PrivateIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP privadas." "PrivateIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP privadas."
"Ads" = "Bloquear Anuncios" "Ads" = "Bloquear Anuncios"
"AdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios." "AdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios."
"Family" = "Bloquear Malware y Contenido para Adultos" "Family" = "Bloquee malware y contenido para adultos"
"FamilyDesc" = "Resolvedores de DNS para bloquear malware y contenido para adultos para protección familiar." "FamilyDesc" = "Resolutores de DNS de Cloudflare para bloquear malware y contenido para adultos para protección familiar."
"Security" = "Bloquee sitios web de malware, phishing y criptomineros"
"SecurityDesc" = "Cambiar la plantilla de configuración para la protección de seguridad."
"Speedtest" = "Bloquear Sitios Web de Pruebas de Velocidad" "Speedtest" = "Bloquear Sitios Web de Pruebas de Velocidad"
"SpeedtestDesc" = "Cambia la plantilla de configuración para evitar la conexión a sitios web de pruebas de velocidad." "SpeedtestDesc" = "Cambia la plantilla de configuración para evitar la conexión a sitios web de pruebas de velocidad."
"IRIp" = "Desactivar Conexión a Rangos de IP de Irán" "IRIp" = "Desactivar Conexión a Rangos de IP de Irán"
@@ -367,23 +382,36 @@
"GoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4." "GoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4."
"NetflixIPv4" = "Usar IPv4 para Netflix" "NetflixIPv4" = "Usar IPv4 para Netflix"
"NetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4." "NetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4."
"GoogleWARP" = "Rutear Google a través de WARP." "GoogleWARP" = "Google"
"GoogleWARPDesc" = "Agregar enrutamiento para Google a través de WARP." "GoogleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
"OpenAIWARP" = "Rutear OpenAI (ChatGPT) a través de WARP." "OpenAIWARP" = "OpenAI (ChatGPT)"
"OpenAIWARPDesc" = "Agregar enrutamiento para OpenAI (ChatGPT) a través de WARP." "OpenAIWARPDesc" = "Enruta el tráfico a OpenAI (ChatGPT) a través de WARP."
"NetflixWARP" = "Rutear Netflix a través de WARP." "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Agregar enrutamiento para Netflix a través de WARP." "NetflixWARPDesc" = "Enruta el tráfico a Netflix a través de WARP."
"SpotifyWARP" = "Rutear Spotify a través de WARP." "MetaWARP" = "Meta"
"SpotifyWARPDesc" = "Agregar enrutamiento para Spotify a través de WARP." "MetaWARPDesc" = "Enruta el tráfico a Meta (Instagram, Facebook, WhatsApp, Threads,...) a través de WARP."
"AppleWARP" = "Apple"
"AppleWARPDesc" = "Enruta el tráfico a Apple a través de WARP."
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "Enruta el tráfico a Reddit a través de WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Enruta el tráfico a Spotify a través de WARP."
"IRWARP" = "Rutear dominios de Irán a través de WARP." "IRWARP" = "Rutear dominios de Irán a través de WARP."
"IRWARPDesc" = "Agregar enrutamiento para dominios de Irán a través de WARP." "IRWARPDesc" = "Agregar enrutamiento para dominios de Irán a través de WARP."
"Inbounds" = "Entrante" "Inbounds" = "Entrante"
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos." "InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"Outbounds" = "Salidas" "Outbounds" = "Salidas"
"Balancers" = "Equilibradores"
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor." "OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"Routings" = "Reglas de enrutamiento" "Routings" = "Reglas de enrutamiento"
"RoutingsDesc" = "¡La prioridad de cada regla es importante!" "RoutingsDesc" = "¡La prioridad de cada regla es importante!"
"completeTemplate" = "Todos" "completeTemplate" = "Todos"
"logLevel" = "Nivel de registro"
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
"accessLog" = "Registro de acceso"
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
"errorLog" = "Registro de errores"
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'ninguno' deshabilitó los registros de errores"
[pages.xray.rules] [pages.xray.rules]
"first" = "Primero" "first" = "Primero"
@@ -394,6 +422,7 @@
"dest" = "Destino" "dest" = "Destino"
"inbound" = "Entrante" "inbound" = "Entrante"
"outbound" = "saliente" "outbound" = "saliente"
"balancer" = "Balancín"
"info" = "Información" "info" = "Información"
"add" = "Agregar regla" "add" = "Agregar regla"
"edit" = "Editar regla" "edit" = "Editar regla"
@@ -414,6 +443,38 @@
"portal" = "portal" "portal" = "portal"
"intercon" = "Interconexión" "intercon" = "Interconexión"
[pages.xray.balancer]
"addBalancer" = "Agregar equilibrador"
"editBalancer" = "Editar balanceador"
"balancerStrategy" = "Estrategia"
"balancerSelectors" = "Selectores"
"tag" = "Etiqueta"
"tagDesc" = "etiqueta única"
"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag."
[pages.xray.wireguard]
"secretKey" = "Llave secreta"
"publicKey" = "Llave pública"
"allowedIPs" = "IP permitidas"
"endpoint" = "Punto final"
"psk" = "Clave precompartida"
"domainStrategy" = "Estrategia de dominio"
[pages.xray.dns]
"enable" = "Habilitar DNS"
"enableDesc" = "Habilitar servidor DNS integrado"
"strategy" = "Estrategia de consulta"
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
"add" = "Agregar servidor"
"edit" = "Editar servidor"
"domains" = "Dominios"
[pages.xray.fakedns]
"add" = "Agregar DNS falso"
"edit" = "Editar DNS falso"
"ipPool" = "Subred del grupo de IP"
"poolSize" = "Tamaño del grupo"
[pages.settings.security] [pages.settings.security]
"admin" = "Administrador" "admin" = "Administrador"
"secret" = "Token Secreto" "secret" = "Token Secreto"
@@ -437,6 +498,7 @@
"noIpRecord" = "❗ ¡Sin Registro de IP!" "noIpRecord" = "❗ ¡Sin Registro de IP!"
"noInbounds" = "❗ ¡No se encontraron entradas!" "noInbounds" = "❗ ¡No se encontraron entradas!"
"unlimited" = "♾ Ilimitado" "unlimited" = "♾ Ilimitado"
"add" = "Agregar"
"month" = "Mes" "month" = "Mes"
"months" = "Meses" "months" = "Meses"
"day" = "Día" "day" = "Día"
@@ -445,6 +507,8 @@
"unknown" = "Desconocido" "unknown" = "Desconocido"
"inbounds" = "Entradas" "inbounds" = "Entradas"
"clients" = "Clientes" "clients" = "Clientes"
"offline" = "🔴 Sin conexión"
"online" = "🟢 En línea"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ Comando desconocido" "unknown" = "❗ Comando desconocido"
@@ -455,8 +519,8 @@
"status" = "✅ ¡El bot está bien!" "status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!" "usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"getID" = "🆔 Tu ID: <code>{{ .ID }}</code>" "getID" = "🆔 Tu ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Buscar un correo electrónico de cliente:\r\n<code>/usage [Email]</code>\r\n \r\nBuscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Nota]</code>" "helpAdminCommands" = "Buscar un correo electrónico de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nBuscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Nota]</code>"
"helpClientCommands" = "Para buscar estadísticas, simplemente usa el siguiente comando:\r\n \r\n<code>/usage [UUID|Contraseña]</code>\r\n \r\nUsa UUID para vmess/vless y Contraseña para Trojan." "helpClientCommands" = "Para buscar estadísticas, simplemente usa el siguiente comando:\r\n\r\n<code>/usage [UUID|Contraseña]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%" "cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%"
@@ -471,7 +535,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n" "ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n"
@@ -485,19 +549,23 @@
"port" = "🔌 Puerto: {{ .Port }}\r\n" "port" = "🔌 Puerto: {{ .Port }}\r\n"
"expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n" "expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n"
"expireIn" = "📅 Vence en: {{ .Time }}\r\n" "expireIn" = "📅 Vence en: {{ .Time }}\r\n"
"active" = "💡 Activo: ✅ Sí\r\n" "active" = "💡 Activo: {{ .Enable }}\r\n"
"inactive" = "💡 Activo: ❌ No\r\n" "enabled" = "🚨 Habilitado: {{ .Enable }}\r\n"
"online" = "🌐 Estado de conexión: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Subida: ↑{{ .Upload }}\r\n" "upload" = "🔼 Subida: ↑{{ .Upload }}\r\n"
"download" = "🔽 Bajada: ↓{{ .Download }}\r\n" "download" = "🔽 Bajada: ↓{{ .Download }}\r\n"
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" "total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n" "TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Agotado {{ .Type }}: \r\n" "exhaustedMsg" = "🚨 Agotado {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}: \r\n" "exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Clientes en línea: {{ .Count }}\r\n"
"disabled" = "🛑 Desactivado: {{ .Disabled }}\r\n" "disabled" = "🛑 Desactivado: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n" "backupTime" = "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n"
"yes" = "✅ Sí"
"no" = "❌ No"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Cerrar Teclado" "closeKeyboard" = "❌ Cerrar Teclado"
@@ -507,11 +575,13 @@
"confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?" "confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?"
"confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?" "confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?"
"confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?" "confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?"
"confirmToggle" = " ✅ ¿Confirmar habilitar/deshabilitar usuario?"
"dbBackup" = "Obtener Copia de Seguridad de BD" "dbBackup" = "Obtener Copia de Seguridad de BD"
"serverUsage" = "Uso del Servidor" "serverUsage" = "Uso del Servidor"
"getInbounds" = "Obtener Entradas" "getInbounds" = "Obtener Entradas"
"depleteSoon" = "Pronto se Agotará" "depleteSoon" = "Pronto se Agotará"
"clientUsage" = "Obtener Uso" "clientUsage" = "Obtener Uso"
"onlines" = "Clientes en línea"
"commands" = "Comandos" "commands" = "Comandos"
"refresh" = "🔄 Actualizar" "refresh" = "🔄 Actualizar"
"clearIPs" = "❌ Limpiar IPs" "clearIPs" = "❌ Limpiar IPs"
@@ -519,14 +589,16 @@
"selectTGUser" = "👤 Seleccionar Usuario de Telegram" "selectTGUser" = "👤 Seleccionar Usuario de Telegram"
"selectOneTGUser" = "👤 Selecciona un usuario de telegram:" "selectOneTGUser" = "👤 Selecciona un usuario de telegram:"
"resetTraffic" = "📈 Reiniciar Tráfico" "resetTraffic" = "📈 Reiniciar Tráfico"
"resetExpire" = "📅 Reiniciar Días de Vencimiento" "resetExpire" = "📅 Cambiar fecha de Vencimiento"
"ipLog" = "🔢 Registro de IP" "ipLog" = "🔢 Registro de IP"
"ipLimit" = "🔢 Límite de IP" "ipLimit" = "🔢 Límite de IP"
"setTGUser" = "👤 Establecer Usuario de Telegram" "setTGUser" = "👤 Establecer Usuario de Telegram"
"toggle" = "🔘 Habilitar / Deshabilitar" "toggle" = "🔘 Habilitar / Deshabilitar"
"custom" = "🔢 Costumbre" "custom" = "🔢 Costumbre"
"confirmNumber" = "✅ Confirmar : {{ .Num }}" "confirmNumber" = "✅ Confirmar: {{ .Num }}"
"confirmNumberAdd" = "✅ Confirmar agregando: {{ .Num }}"
"limitTraffic" = "🚧 Límite de tráfico" "limitTraffic" = "🚧 Límite de tráfico"
"getBanLogs" = "Registros de prohibición"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ ¡Exitosa!" "successfulOperation" = "✅ ¡Exitosa!"
@@ -546,5 +618,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente." "removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente."
"enableSuccess" = "✅ {{ .Email }} : Habilitado exitosamente." "enableSuccess" = "✅ {{ .Email }} : Habilitado exitosamente."
"disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente." "disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente."
"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ID de usuario: <b>{{ .TgUserID }}</b>" "askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ID de usuario: <code>{{ .TgUserID }}</code>"
"askToAddUserName" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su nombre de usuario o ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu nombre de usuario: <b>@{{ .TgUserName }}</b>\r\n\r\nSu ID de usuario: <b>{{ .TgUserID }}</b>"

View File

@@ -1,5 +1,5 @@
"username" = "نام کاربری" "username" = "نامکاربری"
"password" = "رمز عبور" "password" = "رمزعبور"
"login" = "ورود" "login" = "ورود"
"confirm" = "تایید" "confirm" = "تایید"
"cancel" = "انصراف" "cancel" = "انصراف"
@@ -12,7 +12,7 @@
"protocol" = "پروتکل" "protocol" = "پروتکل"
"search" = "جستجو" "search" = "جستجو"
"filter" = "فیلتر" "filter" = "فیلتر"
"loading" = "در حال بروزرسانی..." "loading" = "...در حال بارگذاری"
"second" = "ثانیه" "second" = "ثانیه"
"minute" = "دقیقه" "minute" = "دقیقه"
"hour" = "ساعت" "hour" = "ساعت"
@@ -21,94 +21,97 @@
"indefinite" = "نامحدود" "indefinite" = "نامحدود"
"unlimited" = "نامحدود" "unlimited" = "نامحدود"
"none" = "هیچ" "none" = "هیچ"
"qrCode" = "QR کد" "qrCode" = "QRکد"
"info" = "اطلاعات بیشتر" "info" = "اطلاعات بیشتر"
"edit" = "ویرایش" "edit" = "ویرایش"
"delete" = "حذف" "delete" = "حذف"
"reset" = "ریست" "reset" = "ریست"
"copySuccess" = "با موفقیت کپی شد" "copySuccess" = "باموفقیت کپیشد"
"sure" = "مطمئن" "sure" = "مطمئن"
"encryption" = "رمزگذاری" "encryption" = "رمزگذاری"
"transmission" = "راه اتصال" "transmission" = "راهاتصال"
"host" = "آدرس" "host" = "آدرس"
"path" = "مسیر" "path" = "مسیر"
"camouflage" = "استتار" "camouflage" = "مبهم‌سازی"
"status" = "وضعیت" "status" = "وضعیت"
"enabled" = "فعال" "enabled" = "فعال"
"disabled" = "غیرفعال" "disabled" = "غیرفعال"
"depleted" = "منقضی" "depleted" = "منقضی"
"depletingSoon" = "در حال انقضا" "depletingSoon" = "درحالانقضا"
"offline" = "آفلاین" "offline" = "آفلاین"
"online" = "آنلاین" "online" = "آنلاین"
"domainName" = "آدرس دامنه" "domainName" = "آدرس دامنه"
"monitor" = "آی پی اتصال" "monitor" = "آیپی اتصال"
"certificate" = "گواهی دیجیتال" "certificate" = "گواهی"
"fail" = "خطا" "fail" = "ناموفق"
"success" = " موفق" "success" = " موفق"
"getVersion" = "دریافت ورژن" "getVersion" = "دریافت نسخه"
"install" = "نصب" "install" = "نصب"
"clients" = "کاربران" "clients" = "کاربران"
"usage" = "استفاده" "usage" = "استفاده"
"secretToken" = "توکن امنیتی" "secretToken" = "توکن امنیتی"
"remained" = "باقیمانده" "remained" = "باقیمانده"
"security" = "امنیت" "security" = "امنیت"
"secAlertTitle" = "هشدار‌امنیتی"
"secAlertSsl" = "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید"
"secAlertConf" = "پیکربندی‌های خاصی مستعد حملات سایبری شناسایی شده‌اند، اقدام فوری برای تقویت پروتکل‌های امنیتی و محافظت در برابر نقض‌های امنیتی لازم است"
[menu] [menu]
"dashboard" = "وضعیت سیستم" "dashboard" = "نمای کلی"
"inbounds" = "سرویس ها" "inbounds" = "ورودی‌ها"
"settings" = "تنظیمات پنل" "settings" = "تنظیمات پنل"
"xray" = "الگوی ایکس‌ری" "xray" = "پیکربندی ایکس‌ری"
"logout" = "خروج" "logout" = "خروج"
"link" = "مدیریت" "link" = "مدیریت"
[pages.login] [pages.login]
"title" = "ورود به سیستم" "title" = "خوش‌آمدید"
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید" "loginAgain" = "مدت زمان استفاده بهاتمامرسیده، لطفا دوباره وارد شوید"
[pages.login.toasts] [pages.login.toasts]
"invalidFormData" = "اطلاعات وارد شده به صورت درست وارد نشده است" "invalidFormData" = "اطلاعات به‌درستی وارد نشدهاست"
"emptyUsername" = "نام کاربری خالی میباشد" "emptyUsername" = "لطفا یک نامکاربری وارد کنید‌"
"emptyPassword" = "رمز عبور خالی میباشد" "emptyPassword" = "لطفا یک رمزعبور وارد کنید"
"wrongUsernameOrPassword" = "نام کاربری و رمز عبور اشتباه میباشد" "wrongUsernameOrPassword" = "نامکاربری یا رمزعبوراشتباه‌است"
"successLogin" = "خوش آمدید" "successLogin" = "ورود"
[pages.index] [pages.index]
"title" = "وضعیت سیستم" "title" = "نمای کلی"
"memory" = "حافظه رم" "memory" = "RAM"
"hard" = "حافظه دیسک" "hard" = "Disk"
"xrayStatus" = "وضعیت" "xrayStatus" = "ایکس‌ری"
"stopXray" = "توقف" "stopXray" = "توقف"
"restartXray" = "شروع مجدد" "restartXray" = "شروعمجدد"
"xraySwitch" = "تغییر ورژن" "xraySwitch" = "‌نسخه"
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید" "xraySwitchClick" = سخه مورد نظر را انتخاب کنید"
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد " "xraySwitchClickDesk" = "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمی‌تر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد"
"operationHours" = "آپ تایم سیستم" "operationHours" = "مدت‌کارکرد"
"systemLoad" = "بار سیستم" "systemLoad" = "بارسیستم"
"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته" "systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته"
"connectionTcpCountDesc" = "مجموع اتصالات TCP در تمام کارت های شبکه" "connectionTcpCountDesc" = "در تمام‌شبکه‌ها TCP مجموع‌اتصالات"
"connectionUdpCountDesc" = "مجموع اتصالات UDP در تمام کارت های شبکه" "connectionUdpCountDesc" = "در تمام‌شبکه‌ها UDP مجموع‌اتصالات"
"connectionCount" = "تعداد کانکشن ها" "connectionCount" = "تعداد کانکشن ها"
"upSpeed" = "سرعت آپلود در حال حاضر سیستم" "upSpeed" = "سرعت کلی آپلود در تمام‌شبکه‌ها"
"downSpeed" = "سرعت دانلود در حال حاضر سیستم" "downSpeed" = "سرعت کلی دانلود در تمام‌شبکه‌ها"
"totalSent" = "جمع کل ترافیک آپلود مصرفی" "totalSent" = "مجموع ترافیک ارسال‌‌شده پس‌از شروع‌به‌کار سیستم‌عامل"
"totalReceive" = "جمع کل ترافیک دانلود مصرفی" "totalReceive" = "مجموع ترافیک دریافت‌شده پس‌از شروع‌به‌کار سیستم‌عامل"
"xraySwitchVersionDialog" = "تغییر ورژن" "xraySwitchVersionDialog" = "تغییر نسخه ایکس‌ری"
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین" "xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟"
"dontRefresh" = "در حال نصب ، لطفا رفرش نکنید " "dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
"logs" = "گزارش ها" "logs" = "گزارشها"
"config" = "تنظیمات" "config" = "پیکربندی"
"backup" = "پشتیبان گیری و بازیابی" "backup" = "پشتیبانگیری"
"backupTitle" = "پشتیبان گیری و بازیابی دیتابیس" "backupTitle" = "پشتیبانگیری دیتابیس"
"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید" "backupDescription" = "توصیه‌می‌شود قبلاز واردکردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید"
"exportDatabase" = "دانلود دیتابیس" "exportDatabase" = " پشتیبان‌گیری"
"importDatabase" = "آپلود دیتابیس" "importDatabase" = "بازگرداندن"
[pages.inbounds] [pages.inbounds]
"title" = "کاربران" "title" = "کاربران"
"totalDownUp" = "جمع آپلود/دانلود" "totalDownUp" = "دریافت/ارسال کل"
"totalUsage" = "جمع کل" "totalUsage" = "‌‌‌مصرف کل"
"inboundCount" = "تعداد سرویس ها" "inboundCount" = "کل ورودی‌ها"
"operate" = "فهرست" "operate" = "عملیات"
"enable" = "فعال" "enable" = "فعال"
"remark" = "نام" "remark" = "نام"
"protocol" = "پروتکل" "protocol" = "پروتکل"
@@ -116,71 +119,71 @@
"traffic" = "ترافیک" "traffic" = "ترافیک"
"details" = "توضیحات" "details" = "توضیحات"
"transportConfig" = "نحوه اتصال" "transportConfig" = "نحوه اتصال"
"expireDate" = "تاریخ انقضا" "expireDate" = "مدت زمان"
"resetTraffic" = "ریست ترافیک" "resetTraffic" = "ریست ترافیک"
"addInbound" = "اضافه کردن سرویس" "addInbound" = "افزودن ورودی"
"generalActions" = "عملیات کلی" "generalActions" = "عملیات کلی"
"create" = "اضافه کردن" "create" = "افزودن"
"update" = "ویرایش" "update" = "ویرایش"
"modifyInbound" = "ویرایش سرویس" "modifyInbound" = "ویرایش ورودی"
"deleteInbound" = "حذف سرویس" "deleteInbound" = "حذف ورودی"
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟" "deleteInboundContent" = "آیا مطمئن به حذف ورودی هستید؟"
"deleteClient" = "حذف کاربر" "deleteClient" = "حذف کاربر"
"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید ؟" "deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟"
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید ؟" "resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟"
"copyLink" = "کپی لینک" "copyLink" = "کپی لینک"
"address" = "آدرس" "address" = "آدرس"
"network" = "شبکه" "network" = "شبکه"
"destinationPort" = "پورت مقصد" "destinationPort" = "پورت مقصد"
"targetAddress" = "آدرس مقصد" "targetAddress" = "آدرس مقصد"
"monitorDesc" = "به طور پیش فرض خالی بگذارید" "monitorDesc" = "بهطور پیشفرض خالیبگذارید"
"meansNoLimit" = "یعنی بدون محدودیت" "meansNoLimit" = " = واحد: گیگابایت) نامحدود)"
"totalFlow" = "کل ترافیک" "totalFlow" = "ترافیک کل"
"leaveBlankToNeverExpire" = "خالی بگذارید تا هرگز منقضی نشود" "leaveBlankToNeverExpire" = "برای منقضینشدن خالی‌بگذارید"
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود" "noRecommendKeepDefault" = "توصیهمیشود به‌طور پیشفرض حفظشود"
"certificatePath" = "مسیر فایل گواهی" "certificatePath" = "مسیر فایل"
"certificateContent" = "محتوای فایل گواهی" "certificateContent" = "محتوای فایل"
"publicKeyPath" = "مسیر کلید عمومی" "publicKeyPath" = "مسیر کلید عمومی"
"publicKeyContent" = "محتوای کلید عمومی" "publicKeyContent" = "محتوای کلید عمومی"
"keyPath" = "مسیر کلید خصوصی" "keyPath" = "مسیر کلید خصوصی"
"keyContent" = "محتوای کلید خصوصی" "keyContent" = "محتوای کلید خصوصی"
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید" "clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید"
"client" = "کاربر" "client" = "کاربر"
"export" = "استخراج لینکها" "export" = "استخراج لینکها"
"clone" = "شبیه سازی" "clone" = "شبیهسازی"
"cloneInbound" = "ایجاد" "cloneInbound" = "شبیه‌سازی ورودی"
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد" "cloneInboundContent" = "همه موارد این ورودی بجز پورت، آی‌پی و کاربر‌ها شبیهسازی خواهند شد"
"cloneInboundOk" = "ساختن شبیه ساز" "cloneInboundOk" = "ساختن شبیه ساز"
"resetAllTraffic" = "ریست ترافیک کل سرویس ها" "resetAllTraffic" = "ریست ترافیک کل ورودی‌ها"
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها" "resetAllTrafficTitle" = "ریست ترافیک کل ورودی‌ها"
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟" "resetAllTrafficContent" = "آیا مطمئن به ریست ترافیک تمام ورودی‌ها هستید؟"
"resetInboundClientTraffics" = "ریست ترافیک کاربران" "resetInboundClientTraffics" = "ریست ترافیک کاربران"
"resetInboundClientTrafficTitle" = "ریست ترافیک کل کاربران" "resetInboundClientTrafficTitle" = "ریست ترافیک کاربران"
"resetInboundClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟" "resetInboundClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران این ورودی هستید؟"
"resetAllClientTraffics" = "ریست ترافیک کاربران" "resetAllClientTraffics" = "ریست ترافیک کل کاربران"
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران" "resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران را ریست کنید؟" "resetAllClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران هستید؟"
"delDepletedClients" = "حذف کاربران منقضی" "delDepletedClients" = "حذف کاربران منقضی"
"delDepletedClientsTitle" = "حذف کاربران منقضی" "delDepletedClientsTitle" = "حذف کاربران منقضی"
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟" "delDepletedClientsContent" = "آیا مطمئن به حذف تمام کاربران منقضیشده ‌هستید؟"
"email" = "ایمیل" "email" = "ایمیل"
"emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد" "emailDesc" = "باید یک ایمیل یکتا باشد"
"IPLimit" = "محدودیت ای پی" "IPLimit" = "محدودیت آی‌پی"
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )" "IPLimitDesc" = "(اگر تعداد از مقدار تنظیم شده بیشتر شود، ورودی را غیرفعال می کند. (0 = غیرفعال"
"IPLimitlog" = "گزارش ها" "IPLimitlog" = "گزارشها"
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)" "IPLimitlogDesc" = "گزارش تاریخچه آی‌پی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید"
"IPLimitlogclear" = "پاک کردن گزارش ها" "IPLimitlogclear" = "پاک کردن گزارشها"
"setDefaultCert" = "استفاده از گواهی پنل" "setDefaultCert" = "استفاده از گواهی پنل"
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد" "xtlsDesc" = "ایکس‌ری باید 1.7.5 باشد"
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد" "realityDesc" = "ایکس‌ری باید +1.8.0 باشد"
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)" "telegramDesc" = "دریافت کنید ('/id'یا (دستور (@userinfobot) آی‌دی(های) چت تلگرام مدیر، از"
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید" "subscriptionDesc" = "شما میتوانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین میتوانید از همین نام برای چندین کاربر استفادهکنید"
"info" = "اطلاعات" "info" = "اطلاعات"
"same" = "همسان" "same" = "همسان"
"inboundData" = "داده‌های سرویس" "inboundData" = "داده‌های ورودی"
"copyToClipboard" = "کپی در حافظه" "exportInbound" = "استخراج ورودی"
"import" = ارد کردن" "import" = "افزودن"
"importInbound" = ارد کردن یک سرویس" "importInbound" = "افزودن یک ورودی"
[pages.client] [pages.client]
"add" = "کاربر جدید" "add" = "کاربر جدید"
@@ -188,202 +191,227 @@
"submitAdd" = "اضافه کردن" "submitAdd" = "اضافه کردن"
"submitEdit" = "ذخیره تغییرات" "submitEdit" = "ذخیره تغییرات"
"clientCount" = "تعداد کاربران" "clientCount" = "تعداد کاربران"
"bulk" = "انبوه سازی" "bulk" = "انبوهسازی"
"method" = "روش" "method" = "روش"
"first" = "از" "first" = "از"
"last" = "تا" "last" = "تا"
"prefix" = "پیشوند" "prefix" = "پیشوند"
"postfix" = "پسوند" "postfix" = "پسوند"
"delayedStart" = "شروع بعد از اولین استفاده" "delayedStart" = "شروع‌پس‌ازاولیناستفاده"
"expireDays" = "روزهای اعتبار" "expireDays" = "مدت زمان"
"days" = "(روز)" "days" = "(روز)"
"renew" = "تمدید خودکار" "renew" = "تمدید خودکار"
"renewDesc" = "روزهای تمدید خودکار پس از انقضا. 0 = غیرفعال" "renewDesc" = "(تمدید خودکار پساز انقضا. (0 = غیرفعال)(واحد: روز"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Obtain" "obtain" = "فراهم‌سازی"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "درخواست سربرگ" "request" = "درخواست"
"response" = "پاسخ"
"name" = "نام" "name" = "نام"
"value" = "مقدار" "value" = "مقدار"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "ورژن درخواست" "version" = "نسخه"
"requestMethod" = "متد درخواست" "method" = "متد"
"requestPath" = "مسیر درخواست" "path" = "مسیر"
"responseVersion" = رژن پاسخ" "status" = ضعیت"
"responseStatus" = "وضعیت پاسخ" "statusDescription" = "توضیحات وضعیت"
"responseStatusDescription" = "توضیحات وضعیت پاسخ" "requestHeader" = "سربرگ درخواست"
"responseHeader" = "سربرگ پاسخ" "responseHeader" = "سربرگ پاسخ"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "رمزنگاری" "encryption" = "رمزنگاری"
[pages.settings] [pages.settings]
"title" = "تنظیمات" "title" = "تنظیمات پنل"
"save" = "ذخیره" "save" = "ذخیره"
"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید" "infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید"
"restartPanel" = "ریستارت پنل" "restartPanel" = "ریستارت پنل"
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید" "restartPanelDesc" = "آیا مطمئن به ریستارت پنل هستید؟ اگر پس‌از ریستارت نمیتوانید به پنل دسترسی پیدا کنید، لطفاً گزارش‌های موجود در اسکریپت پنل را بررسی کنید"
"actions" = "عملیات ها" "actions" = "عملیات ها"
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض" "resetDefaultConfig" = "برگشت به پیشفرض"
"panelSettings" = "تنظیمات پنل" "panelSettings" = "پیکربندی"
"securitySettings" = "تنظیمات امنیتی" "securitySettings" = "احرازهویت"
"TGBotSettings" = "تنظیمات ربات تلگرام" "TGBotSettings" = "ربات تلگرام"
"panelListeningIP" = "محدودیت آی پی پنل" "panelListeningIP" = "آدرس آیپی"
"panelListeningIPDesc" = "برای استفاده از تمام آی‌پیها به طور پیش فرض خالی بگذارید" "panelListeningIPDesc" = "آدرس آی‌پی برای وب پنل. برای گوش‌دادن به‌تمام آی‌پیها خالیبگذارید"
"panelListeningDomain" = "محدودیت دامین پنل" "panelListeningDomain" = "نام دامنه"
"panelListeningDomainDesc" = "برای استفاده از تمام دامنه‌ها و آی‌پی‌ها به طور پیش فرض خالی بگذارید" "panelListeningDomainDesc" = "آدرس دامنه برای وب پنل. برای گوش دادن به‌تمام دامنه‌ها و آی‌پی‌ها خالیبگذارید"
"panelPort" = "پورت پنل" "panelPort" = "پورت"
"panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل" "panelPortDesc" = "شماره پورت برای وب پنل. باید پورت استفاده نشده‌باشد"
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل" "publicKeyPath" = "مسیر کلید عمومی"
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود " "publicKeyPathDesc" = "مسیر فایل کلیدعمومی برای وب پنل. با '/' شروعمیشود"
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل" "privateKeyPath" = "مسیر کلید خصوصی"
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود " "privateKeyPathDesc" = "مسیر فایل کلیدخصوصی برای وب پنل. با '/' شروعمیشود"
"panelUrlPath" = "آدرس روت پنل" "panelUrlPath" = "URI مسیر"
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود" "panelUrlPathDesc" = رای وب پنل. با '/' شروع و با '/' خاتمه‌ می‌یابد URI مسیر"
"pageSize" = "اندازه صفحه بندی جدول" "pageSize" = "اندازه صفحه بندی جدول"
"pageSizeDesc" = "اندازه صفحه را برای جدول سرویس ها تعریف کنید. 0: غیرفعال" "pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال"
"remarkModel" = "نام کانفیگ و جداکننده" "remarkModel" = "نامکانفیگ و جداکننده"
"sampleRemark" = مونه نام" "datepicker" = وع تقویم"
"oldUsername" = "نام کاربری فعلی" "datepickerPlaceholder" = "انتخاب تاریخ"
"currentPassword" = "رمز عبور فعلی" "datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود"
"newUsername" = "نام کاربری جدید" "sampleRemark" = "نمونه‌نام"
"newPassword" = "رمز عبور جدید" "oldUsername" = "نام‌کاربری فعلی"
"telegramBotEnable" = "فعالسازی ربات تلگرام" "currentPassword" = "رمز‌عبور فعلی"
"telegramBotEnableDesc" = "از طریق بات تلگرام به امکانات این پنل متصل شوید" "newUsername" = "نام‌کاربری جدید"
"newPassword" = "رمزعبور جدید"
"telegramBotEnable" = "فعال‌سازی ربات تلگرام"
"telegramBotEnableDesc" = "ربات تلگرام را فعال می‌کند"
"telegramToken" = "توکن تلگرام" "telegramToken" = "توکن تلگرام"
"telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather" "telegramTokenDesc" = "دریافت کنید @botfather توکن را می‌توانید از"
"telegramChatId" = "آی دی تلگرام مدیریت" "telegramProxy" = "SOCKS پراکسی"
"telegramChatIdDesc" = "از @userinfobot یا دستور '/id' در ربات برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. " "telegramProxyDesc" = "را برای اتصال به تلگرام فعال می کند SOCKS5 پراکسی"
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام" "telegramChatId" = "آی‌دی چت مدیر"
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید " "telegramChatIdDesc" = "دریافت ‌کنید ('/id'یا (دستور (@userinfobot) آی‌دی(های) چت تلگرام مدیر، از"
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده" "telegramNotifyTime" = "زمان نوتیفیکیشن"
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای" "telegramNotifyTimeDesc" = "زمان‌اطلاع‌رسانی ربات تلگرام برای گزارش های دوره‌ای. از فرمت زمانبندی لینوکس استفاده‌کنید‌"
"tgNotifyBackup" = "پشتیبان‌گیری از دیتابیس"
"tgNotifyBackupDesc" = "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند"
"tgNotifyLogin" = "اعلان ورود" "tgNotifyLogin" = "اعلان ورود"
"tgNotifyLoginDesc" = "نام کاربری، آدرس ای پی، و زمان وقتی که فردی سعی می‌کند به پنل شما وارد شود نمایش میدهد" "tgNotifyLoginDesc" = "نامکاربری، آدرس آی‌پی، و زمان ورود، فردی که سعی می‌کند وارد پنل شود را نمایش میدهد"
"sessionMaxAge" = "بیشینه زمان جلسه وب" "sessionMaxAge" = "بیشینه زمان جلسه وب"
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)" "sessionMaxAgeDesc" = "(بیشینه زمانی که میتوانید لاگین بمانید. (واحد: دقیقه"
"expireTimeDiff" = "آستانه زمان باقی مانده" "expireTimeDiff" = "آستانه زمان باقی مانده"
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)" "expireTimeDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به زمان انقضا. (واحد: روز"
"trafficDiff" = "آستانه ترافیک باقی مانده" "trafficDiff" = "آستانه ترافیک باقی مانده"
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)" "trafficDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به اتمام ترافیک. (واحد: گیگابایت"
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده" "tgNotifyCpu" = "آستانه هشدار بار پردازنده"
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)" "tgNotifyCpuDesc" = "(اگر بار روی پردازنده ازاین آستانه فراتر رفت، برای شما پیام ارسال می‌شود. (واحد: درصد"
"timeZone" = "منظقه زمانی" "timeZone" = "منطقه زمانی"
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود" "timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقهزمانی اجرا میشود"
"subSettings" = "سابسکریپشن" "subSettings" = "سابسکریپشن"
"subEnable" = "فعال کردن سرویس" "subEnable" = "فعال‌سازی سرویس سابسکریپشن"
"subEnableDesc" = "ویژگی سابسکریپشن با پیکربندی جداگانه" "subEnableDesc" = " سرویس سابسکریپشن را فعال‌می‌کند"
"subListen" = "محدودیت آی‌پی" "subListen" = "آدرس آی‌پی"
"subListenDesc" = "برای استفاده از همه آی‌پی ها به طور پیش فرض خالی بگذارید" "subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پیها خالیبگذارید"
"subPort" = "پورت سرویس" "subPort" = "پورت"
"subPortDesc" = "شماره پورت برای ارائه خدمات سابسکریپشن باید خالی باشد" "subPortDesc" = "شماره پورت برای سرویس سابسکریپشن. باید پورت استفاده نشده‌باشد"
"subCertPath" = "مسیر فایل کلید عمومی گواهی سابسکریپشن" "subCertPath" = "مسیر کلید عمومی"
"subCertPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید." "subCertPathDesc" = "مسیر فایل کلیدعمومی برای سرویس سابیکریپشن. با '/' شروعمیشود"
"subKeyPath" = "مسیر فایل کلید خصوصی گواهی سابسکریپشن" "subKeyPath" = "مسیر کلید خصوصی"
"subKeyPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید." "subKeyPathDesc" = "مسیر فایل کلیدخصوصی برای سرویس سابسکریپشن. با '/' شروعمیشود"
"subPath" = "مسیر ریشه سابسکریپشن" "subPath" = "URI مسیر"
"subPathDesc" = "باید با '/' شروع شود و با '/' ختم شود." "subPathDesc" = رای سرویس سابسکریپشن. با '/' شروع و با '/' خاتمه می‌یابد URI مسیر"
"subDomain" = "دامنه مخصوص سابسکریپشن" "subDomain" = "نام دامنه"
"subDomainDesc" = "برای نظارت بر همه دامنه ها و آی‌پی ها به طور پیش فرض خالی بگذارید" "subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آی‌پیها خالیبگذارید"
"subUpdates" = "فاصله به روز رسانی های سابسکریپشن" "subUpdates" = "فاصله بروزرسانی سابسکریپشن"
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر" "subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت"
"subEncrypt" = "رمزگذاری کانفیگ ها" "subEncrypt" = "کدگذاری"
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن" "subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
"subShowInfo" = "نمایش اطلاعات مصرف" "subShowInfo" = "نمایش اطلاعات مصرف"
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد" "subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامه‌های کاربری نمایش میدهد"
"subURI" = "آدرس پایه پروکسی معکوس" "subURI" = "پروکسی معکوس URI مسیر"
"subURIDesc" = "آدرس پایه سابسکریپشن را برای استفاده در پشت پراکسی ها تغییر میدهد" "subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
"fragment" = "تکه‌تکه شدن"
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تی‌ال‌اس"
[pages.xray] [pages.xray]
"title" = "الگوها" "title" = "پیکربندی ایکس‌ری"
"save" = "ذخیره تنظیمات" "save" = "ذخیره"
"restart" = "ریستارت ایکس‌ری" "restart" = "ریستارت ایکس‌ری"
"basicTemplate" = "بخش الگو پایه" "basicTemplate" = "پایه"
"advancedTemplate" = "بخش الگو پیشرفته" "advancedTemplate" = "پیشرفته"
"generalConfigs" = "تنظیمات عمومی" "generalConfigs" = "استراتژی‌ کلی"
"generalConfigsDesc" = "این تنظیمات میتواند ترافیک کلی سرویس را متاثر کند" "generalConfigsDesc" = "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند"
"blockConfigs" = "مسدود سازی" "logConfigs" = "گزارش"
"blockConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند" "logConfigsDesc" = "گزارش‌ها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید"
"blockCountryConfigs" = "تنظیمات برای مسدودسازی کشورها" "blockConfigs" = "سپر محافظ"
"blockCountryConfigsDesc" = "این گزینه اتصال کاربران به دامنه های کشوری خاص را مسدود می کند" "blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود میکند"
"directCountryConfigs" = "تنظیمات برای اتصال مستقیم کشورها" "blockCountryConfigs" = "مسدودسازی کشور"
"directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند" "blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
"ipv4Configs" = "تنظیمات برای IPv4" "directCountryConfigs" = "اتصال مستقیم کشور"
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود" "directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال می‌کند"
"warpConfigs" = "تنظیمات برای WARP" "ipv4Configs" = "IPv4 مسیریابی"
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند" "ipv4ConfigsDesc" = "این گزینه‌ها ترافیک را از طریق آیپینسخه4 به مقصد هدایت میکند"
"Template" = "تنظیمات الگو ایکس ری" "warpConfigs" = "WARP مسیریابی"
"TemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!" "warpConfigsDesc" = "طبق راهنما نصب کنید SOCKS5 این گزینه‌ها ترافیک‌ را از طریق وارپ کلادفلر به مقصد هدایت می‌کند. ابتدا، وارپ را در حالت پراکسی"
"FreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم" "Template" = "‌پیکربندی پیشرفته الگو ایکس‌ری"
"FreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم" "TemplateDesc" = "فایل پیکربندی نهایی ایکس‌ری بر اساس این الگو ایجاد می‌شود"
"RoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی" "FreedomStrategy" = "Freedom استراتژی پروتکل"
"RoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه" "FreedomStrategyDesc" = "تعیین می‌کند Freedom استراتژی خروجی شبکه را برای پروتکل"
"Torrent" = "فیلتر کردن بیت تورنت" "RoutingStrategy" = "استراتژی کلی مسیریابی"
"TorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد" "RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواست‌ها را تعیین می‌کند"
"PrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی" "Torrent" = "مسدودسازی پروتکل بیت‌تورنت"
"PrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد" "TorrentDesc" = "پروتکل بیت تورنت را مسدود می‌کند"
"Ads" = "مسدود کردن تبلیغات" "PrivateIp" = "مسدودسازی اتصال آی‌پی‌های خصوصی"
"AdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد" "PrivateIpDesc" = "اتصال به آی‌پی‌های رنج خصوصی را مسدود می‌کند"
"Family" = "فعال کردن حالت خانواده" "Ads" = "مسدودسازی تبلیغات"
"FamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن" "AdsDesc" = "وب‌سایت‌های تبلیغاتی را مسدود می‌کند"
"Speedtest" = "جلوگیری از اتصال به سایت های تست سرعت" "Family" = "محافظت خانواده"
"SpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد" "FamilyDesc" = "محتوای مخصوص بزرگسالان، و وب‌سایتهای ناامن را مسدود می‌کند"
"IRIp" = "جلوگیری از اتصال آیپی های ایران" "Security" = "محافظت امنیتی"
"IRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد" "SecurityDesc" = "وب‌سایت‌های ناامن، بدافزار، فیشینگ، و کریپتوماینرها را مسدود می‌کند"
"IRDomain" = "جلوگیری از اتصال دامنه های ایران" "Speedtest" = "مسدودسازی اسپیدتست"
"IRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد" "SpeedtestDesc" = "اتصال به وب‌سایت‌های تست سرعت را مسدود می‌کند"
"ChinaIp" = "جلوگیری از اتصال آیپی های چین" "IRIp" = "مسدودسازی اتصال به آیپیهای ایران"
"ChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد" "IRIpDesc" = "اتصال به آیپیهای کشور ایران را مسدود می‌کند"
"ChinaDomain" = "جلوگیری از اتصال دامنه های چین" "IRDomain" = "مسدودسازی اتصال به دامنههای ایران"
"ChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد" "IRDomainDesc" = "اتصال به دامنههای کشور ایران را مسدود می‌کند"
"RussiaIp" = "جلوگیری از اتصال آیپی های روسیه" "ChinaIp" = "مسدودسازی اتصال به آی‌‌پیهای چین"
"RussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد" "ChinaIpDesc" = "اتصال به آیپیهای کشور چین را مسدود می‌کند"
"RussiaDomain" = "جلوگیری از اتصال دامنه های روسیه" "ChinaDomain" = "مسدودسازی اتصال به دامنههای چین"
"RussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد" "ChinaDomainDesc" = "اتصال به دامنههای کشور چین را مسدود می‌کند"
"VNIp" = "جلوگیری از اتصال آیپی های ویتنام" "RussiaIp" = "مسدودسازی اتصال به آیپیهای روسیه"
"VNIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ویتنام تغییر میدهد" "RussiaIpDesc" = "اتصال به آیپیهای کشور روسیه را مسدود می‌کند"
"VNDomain" = "جلوگیری از اتصال دامنه های ویتنام" "RussiaDomain" = "مسدودسازی اتصال به دامنههای روسیه"
"VNDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ویتنام تغییر میدهد" "RussiaDomainDesc" = "اتصال به دامنههای کشور روسیه را مسدود می‌کند"
"DirectIRIp" = "ارتباط مستقیم به آیپی های ایران" "VNIp" = "مسدودسازی اتصال به آیپیهای ویتنام"
"DirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد" "VNIpDesc" = "اتصال به آیپیهای کشور ویتنام را مسدود می‌کند"
"DirectIRDomain" = "ارتباط مستقیم به دامنه های ایران" "VNDomain" = "مسدودسازی اتصال به دامنه های ویتنام"
"DirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد" "VNDomainDesc" = "اتصال به دامنههای کشور ویتنام را مسدود می‌کند"
"DirectChinaIp" = "ارتباط مستقیم به آیپی های چین" "DirectIRIp" = "اتصال مستقیم آیپیهای ایران"
"DirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد" "DirectIRIpDesc" = "اتصال مستقیم به آیپیهای کشور ایران"
"DirectChinaDomain" = "ارتباط مستقیم به دامنه های چین" "DirectIRDomain" = "اتصال مستقیم دامنههای ایران"
"DirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد" "DirectIRDomainDesc" = "اتصال مستقیم به دامنههای کشور ایران"
"DirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه" "DirectChinaIp" = "اتصال مستقیم آیپیهای چین"
"DirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد" "DirectChinaIpDesc" = "اتصال مستقیم به آیپیهای کشور چین"
"DirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه" "DirectChinaDomain" = "اتصال مستقیم دامنههای چین"
"DirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد" "DirectChinaDomainDesc" = "اتصال مستقیم به دامنههای کشور چین"
"DirectVNIp" = "اتصال مستقیم به ای پی های ویتنام" "DirectRussiaIp" = "اتصال مستقیم آی‌پیهای روسیه"
"DirectVNIpDesc" = "الگوی پیکربندی را برای اتصال مستقیم به محدوده آی پی های ویتنام تغییر میدهد" "DirectRussiaIpDesc" = "اتصال مستقیم به آیپیهای کشور روسیه"
"DirectVNDomain" = "اتصال مستقیم به دامنه های ویتنام" "DirectRussiaDomain" = "اتصال مستقیم دامنههای روسیه"
"DirectVNDomainDesc" = "الگوی پیکربندی را برای اتصال مستقیم به دامنه های ویتنام تغییر میدهد" "DirectRussiaDomainDesc" = "اتصال مستقیم به دامنههای کشور روسیه"
"GoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل" "DirectVNIp" = "اتصال مستقیم آیپی‌های ویتنام"
"GoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند" "DirectVNIpDesc" = "اتصال مستقیم به آیپی‌های کشور ویتنام"
"NetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس" "DirectVNDomain" = "اتصال مستقیم دامنه‌های ویتنام"
"NetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند" "DirectVNDomainDesc" = "اتصال مستقیم به دامنه‌های کشور ویتنام"
"GoogleWARP" = "مسیردهی گوگل به WARP" "GoogleIPv4" = "گوگل"
"GoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند" "GoogleIPv4Desc" = "ترافیک را از طریق آیپینسخه4 به گوگل هدایت میکند"
"OpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP" "NetflixIPv4" = "نتفلیکس"
"OpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند" "NetflixIPv4Desc" = "ترافیک را از طریق آیپینسخه4 به نتفلیکس هدایت میکند"
"NetflixWARP" = "مسیردهی نتفلیکس به WARP" "GoogleWARP" = "گوگل"
"NetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند" "GoogleWARPDesc" = "ترافیک را از طریق وارپ به گوگل هدایت میکند"
"SpotifyWARP" = "مسیردهی اسپاتیفای به WARP" "OpenAIWARP" = "چت جی‌پی‌تی"
"SpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند" "OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جی‌پی‌تی هدایت میکند"
"IRWARP" = "مسیردهی دامنه های ایران به WARP" "NetflixWARP" = "نتفلیکس"
"IRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند" "NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
"MetaWARP" = "متا"
"MetaWARPDesc" = "ترافیک را از طریق وارپ به متا (اینستاگرام، فیس بوک، واتساپ، تردز و...) هدایت می کند."
"AppleWARP" = "اپل"
"AppleWARPDesc" = " ترافیک را از طریق وارپ به اپل هدایت می‌کند"
"RedditWARP" = "ردیت"
"RedditWARPDesc" = " ترافیک را از طریق وارپ به ردیت هدایت می‌کند"
"SpotifyWARP" = "اسپاتیفای"
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت می‌کند"
"IRWARP" = "دامنه‌های ایران"
"IRWARPDesc" = "ترافیک را از طریق وارپ به دامنه‌های کشور ایران هدایت می‌کند"
"Inbounds" = "ورودی‌ها" "Inbounds" = "ورودی‌ها"
"InboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید" "InboundsDesc" = "پذیرش کلاینت خاص"
"Outbounds" = "خروجی‌ها" "Outbounds" = "خروجی‌ها"
"OutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید" "Balancers" = "بالانسرها"
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
"Routings" = "قوانین مسیریابی" "Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است!" "RoutingsDesc" = "اولویت هر قانون مهم است"
"completeTemplate" = "کامل" "completeTemplate" = "کامل"
"logLevel" = "سطح گزارش"
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
"accessLog" = "مسیر گزارش"
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند."
"errorLog" = "گزارش خطا"
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
[pages.xray.rules] [pages.xray.rules]
"first" = "اولین" "first" = "اولین"
@@ -394,6 +422,7 @@
"dest" = "مقصد" "dest" = "مقصد"
"inbound" = "ورودی" "inbound" = "ورودی"
"outbound" = "خروجی" "outbound" = "خروجی"
"balancer" = "بالانسر"
"info" = "اطلاعات" "info" = "اطلاعات"
"add" = "افزودن قانون" "add" = "افزودن قانون"
"edit" = "ویرایش قانون" "edit" = "ویرایش قانون"
@@ -411,23 +440,55 @@
"domain" = "دامنه" "domain" = "دامنه"
"type" = "نوع" "type" = "نوع"
"bridge" = "پل" "bridge" = "پل"
"portal" = "پرتال" "portal" = ورتال"
"intercon" = "اتصال میانی" "intercon" = "اتصال میانی"
[pages.xray.balancer]
"addBalancer" = "افزودن بالانسر"
"editBalancer" = "ویرایش بالانسر"
"balancerStrategy" = "استراتژی"
"balancerSelectors" = "انتخاب‌گرها"
"tag" = "برچسب"
"tagDesc" = "برچسب یگانه"
"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
[pages.xray.wireguard]
"secretKey" = "کلید شخصی"
"publicKey" = "کلید عمومی"
"allowedIPs" = "آی‌پی‌های مجاز"
"endpoint" = "نقطه پایانی"
"psk" = "کلید مشترک"
"domainStrategy" = "استراتژی حل دامنه"
[pages.xray.dns]
"enable" = "فعال کردن حل دامنه"
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
"strategy" = "استراتژی پرس‌وجو"
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
"add" = "افزودن سرور"
"edit" = "ویرایش سرور"
"domains" = "دامنه‌ها"
[pages.xray.fakedns]
"add" = "افزودن دی‌ان‌اس جعلی"
"edit" = "ویرایش دی‌ان‌اس جعلی"
"ipPool" = "زیرشبکه استخر آی‌پی"
"poolSize" = "اندازه استخر"
[pages.settings.security] [pages.settings.security]
"admin" = "مدیر" "admin" = "مدیر"
"secret" = "توکن امنیتی" "secret" = "توکن مخفی"
"loginSecurity" = "لاگین ایمن" "loginSecurity" = "ورود ایمن"
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین" "loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
"secretToken" = "توکن امنیتی" "secretToken" = "توکن مخفی"
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه دارید، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!" "secretTokenDesc" = "لطفاً این توکن را در مکانی امن ذخیره کنید. این توکن برای ورود به سیستم مورد نیاز است و قابل بازیابی نیست"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "ویرایش تنظیمات" "modifySettings" = "ویرایش تنظیمات"
"getSettings" = "دریافت تنظیمات" "getSettings" = "دریافت تنظیمات"
"modifyUser" = "ویرایش کاربر" "modifyUser" = "ویرایش مدیر"
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد " "originalUserPassIncorrect" = "نامکاربری یا رمزعبور فعلی اشتباه‌است"
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد " "userPassMustBeNotEmpty" = "نامکاربری یا رمزعبور جدید خالی‌است"
[tgbot] [tgbot]
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!" "keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
@@ -436,15 +497,18 @@
"wentWrong" = "❌ مشکلی رخ داده است!" "wentWrong" = "❌ مشکلی رخ داده است!"
"noIpRecord" = "❗ رکورد IP یافت نشد!" "noIpRecord" = "❗ رکورد IP یافت نشد!"
"noInbounds" = "❗ هیچ ورودی یافت نشد!" "noInbounds" = "❗ هیچ ورودی یافت نشد!"
"unlimited" = "♾ نامحدود" "unlimited" = "♾ - نامحدود(ریست)"
"add" = "اضافه کردن"
"month" = "ماه" "month" = "ماه"
"months" = "ماه‌ها" "months" = "ماه‌ها"
"day" = "روز" "day" = "روز"
"days" = "روزها" "days" = "روزها"
"hours" = "ساعت ها" "hours" = "ساعتها"
"unknown" = "نامشخص" "unknown" = "نامشخص"
"inbounds" = "ورودی‌ها" "inbounds" = "ورودی‌ها"
"clients" = "کلاینت‌ها" "clients" = "کلاینت‌ها"
"offline" = "🔴 آفلاین"
"online" = "🟢 آنلاین"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ دستور ناشناخته" "unknown" = "❗ دستور ناشناخته"
@@ -455,63 +519,69 @@
"status" = "✅ ربات در حالت عادی است!" "status" = "✅ ربات در حالت عادی است!"
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>" "getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>" "helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید." "helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است." "cpuThreshold" = "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%"
"selectUserFailed" = "❌ خطا در انتخاب کاربر!" "selectUserFailed" = "❌ خطا در انتخاب کاربر!"
"userSaved" = "✅ کاربر تلگرام ذخیره شد." "userSaved" = "✅ کاربر تلگرام ذخیره شد."
"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n" "loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
"loginFailed" = "❗️ ورود به پنل ناموفق بود.\r\n" "loginFailed" = "❗️ ورود به پنل ناموفقبود \r\n"
"report" = "🕰 گزارشات زمان‌بندی شده: {{ .RunTime }}\r\n" "report" = "🕰 گزارشاتزمان‌بندیشده: {{ .RunTime }}\r\n"
"datetime" = "⏰ تاریخ-زمان: {{ .DateTime }}\r\n" "datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n"
"hostname" = "💻 نام میزبان: {{ .Hostname }}\r\n" "hostname" = "💻 ناممیزبان: {{ .Hostname }}\r\n"
"version" = "🚀 نسخه X-UI: {{ .Version }}\r\n" "version" = "🚀 نسخه‌پنل: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 آدرس IP: {{ .IP }}\r\n" "ip" = "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n"
"ips" = "🔢 آدرس‌های IP: \r\n{{ .IPs }}\r\n" "ips" = "🔢 آدرس‌های آی‌پی:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ مدت‌کارکردسیستم: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 بارسیستم: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 تعداد ترافیک TCP: {{ .Count }}\r\n" "tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 تعداد ترافیک UDP: {{ .Count }}\r\n" "udpCount" = "🔸 UDP: {{ .Count }}\r\n"
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " وضعیت Xray: {{ .State }}\r\n" "xrayStatus" = " وضعیت‌ایکس‌ری: {{ .State }}\r\n"
"username" = "👤 نام کاربری: {{ .Username }}\r\n" "username" = "👤 نامکاربری: {{ .Username }}\r\n"
"time" = "⏰ زمان: {{ .Time }}\r\n" "time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 ورودی: {{ .Remark }}\r\n" "inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n" "port" = "🔌 پورت: {{ .Port }}\r\n"
"expire" = "📅 تاریخ انقضا: {{ .Time }}\r\n" "expire" = "📅 تاریخانقضا: {{ .Time }}\r\n\r\n"
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n" "expireIn" = "📅 باقیمانده‌تاانقضا: {{ .Time }}\r\n\r\n"
"active" = "💡 فعال: \r\n" "active" = "💡 فعال: {{ .Enable }}\r\n"
"inactive" = "💡 فعال: ❌\r\n" "enabled" = "🚨 وضعیت: {{ .Enable }}\r\n"
"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n"
"email" = "📧 ایمیل: {{ .Email }}\r\n" "email" = "📧 ایمیل: {{ .Email }}\r\n"
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" "upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
"download" = "🔽 دانلود↓: {{ .Download }}\r\n" "download" = "🔽 دانلود↓: {{ .Download }}\r\n"
"total" = "📊 کل: {{ .UpDown }} / {{ .Total }}\r\n" "total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n" "TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n" "exhaustedMsg" = "🚨 {{ .Type }} بهاتمامرسیدهاست:\r\n"
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n" "exhaustedCount" = "🚨 تعداد {{ .Type }} بهاتمامرسیده‌است:\r\n"
"onlinesCount" = "🌐 کاربران‌آنلاین: {{ .Count }}\r\n"
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" "disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 بهزودیبهپایانخواهدرسید: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 زمان پشتیبان‌گیری: {{ .Time }}\r\n" "backupTime" = "🗄 زمانپشتیبان‌گیری: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n"
"yes" = "✅ بله"
"no" = "❌ خیر"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ بستن کیبورد" "closeKeyboard" = "❌ بستن کیبورد"
"cancel" = "❌ لغو" "cancel" = "❌ لغو"
"cancelReset" = "❌ لغو تنظیم مجدد" "cancelReset" = "❌ لغو تنظیم مجدد"
"cancelIpLimit" = "❌ لغو محدودیت IP" "cancelIpLimit" = "❌ لغو محدودیت آی‌پی"
"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟" "confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟"
"confirmClearIps" = "✅ تأیید پاک‌سازی آدرس‌های IP؟" "confirmClearIps" = "✅ تأیید پاک‌سازی آدرس‌های آی‌پی؟"
"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟" "confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟"
"dbBackup" = "دریافت پشتیبان پایگاه داده" "confirmToggle" = "✅ تایید فعال/غیرفعال کردن کاربر؟"
"serverUsage" = "استفاده از سرور" "dbBackup" = "دریافت پشتیبان"
"serverUsage" = "استفاده از سیستم"
"getInbounds" = "دریافت ورودی‌ها" "getInbounds" = "دریافت ورودی‌ها"
"depleteSoon" = "به زودی به پایان خواهد رسید" "depleteSoon" = "بهزودی به پایان خواهد رسید"
"clientUsage" = "دریافت آمار کاربر" "clientUsage" = "دریافت آمار کاربر"
"onlines" = "کاربران آنلاین"
"commands" = "دستورات" "commands" = "دستورات"
"refresh" = "🔄 تازه‌سازی" "refresh" = "🔄 تازه‌سازی"
"clearIPs" = "❌ پاک‌سازی آدرس‌ها" "clearIPs" = "❌ پاک‌سازی آدرس‌ها"
@@ -525,8 +595,10 @@
"setTGUser" = "👤 تنظیم کاربر تلگرام" "setTGUser" = "👤 تنظیم کاربر تلگرام"
"toggle" = "🔘 فعال / غیرفعال" "toggle" = "🔘 فعال / غیرفعال"
"custom" = "🔢 سفارشی" "custom" = "🔢 سفارشی"
"confirmNumber" = "✅ تایید : {{ .Num }}" "confirmNumber" = "✅ تایید: {{ .Num }}"
"confirmNumberAdd" = "✅ تایید اضافه کردن: {{ .Num }}"
"limitTraffic" = "🚧 محدودیت ترافیک" "limitTraffic" = "🚧 محدودیت ترافیک"
"getBanLogs" = "گزارش های بلوک را دریافت کنید"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ انجام شد!" "successfulOperation" = "✅ انجام شد!"
@@ -546,5 +618,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد." "removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد."
"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد." "enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد."
"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد." "disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد."
"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>" "askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <code>{{ .TgUserID }}</code>"
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که نام کاربری یا شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"

View File

@@ -0,0 +1,621 @@
"username" = "Nama Pengguna"
"password" = "Kata Sandi"
"login" = "Masuk"
"confirm" = "Konfirmasi"
"cancel" = "Batal"
"close" = "Tutup"
"copy" = "Salin"
"copied" = "Tersalin"
"download" = "Unduh"
"remark" = "Catatan"
"enable" = "Aktifkan"
"protocol" = "Protokol"
"search" = "Cari"
"filter" = "Filter"
"loading" = "Memuat..."
"second" = "Detik"
"minute" = "Menit"
"hour" = "Jam"
"day" = "Hari"
"check" = "Centang"
"indefinite" = "Tak Terbatas"
"unlimited" = "Tanpa Batas"
"none" = "None"
"qrCode" = "Kode QR"
"info" = "Informasi Lebih Lanjut"
"edit" = "Edit"
"delete" = "Hapus"
"reset" = "Reset"
"copySuccess" = "Berhasil Disalin"
"sure" = "Yakin"
"encryption" = "Enkripsi"
"transmission" = "Transmisi"
"host" = "Host"
"path" = "Jalur"
"camouflage" = "Obfuscation"
"status" = "Status"
"enabled" = "Aktif"
"disabled" = "Nonaktif"
"depleted" = "Habis"
"depletingSoon" = "Akan Habis"
"offline" = "Offline"
"online" = "Online"
"domainName" = "Nama Domain"
"monitor" = "IP Pemantauan"
"certificate" = "Sertifikat"
"fail" = "Gagal"
"success" = "Berhasil"
"getVersion" = "Dapatkan Versi"
"install" = "Instal"
"clients" = "Klien"
"usage" = "Penggunaan"
"secretToken" = "Token Rahasia"
"remained" = "Tersisa"
"security" = "Keamanan"
"secAlertTitle" = "Peringatan keamanan"
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
"secAlertConf" = "Konfigurasi tertentu telah diidentifikasi rentan terhadap serangan, sehingga mendorong tindakan segera untuk memperkuat protokol keamanan dan melindungi dari potensi pelanggaran keamanan."
[menu]
"dashboard" = "Ikhtisar"
"inbounds" = "Masuk"
"settings" = "Pengaturan Panel"
"xray" = "Konfigurasi Xray"
"logout" = "Keluar"
"link" = "Kelola"
[pages.login]
"title" = "Selamat Datang"
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
[pages.login.toasts]
"invalidFormData" = "Format data input tidak valid."
"emptyUsername" = "Nama Pengguna diperlukan"
"emptyPassword" = "Kata Sandi diperlukan"
"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
"successLogin" = "Login berhasil"
[pages.index]
"title" = "Ikhtisar"
"memory" = "RAM"
"hard" = "Disk"
"xrayStatus" = "Xray"
"stopXray" = "Stop"
"restartXray" = "Restart"
"xraySwitch" = "Versi"
"xraySwitchClick" = "Pilih versi yang ingin Anda pindah."
"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini."
"operationHours" = "Waktu Aktif"
"systemLoad" = "Beban Sistem"
"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
"connectionCount" = "Statistik Koneksi"
"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem"
"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem"
"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS"
"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS"
"xraySwitchVersionDialog" = "Ganti Versi Xray"
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log"
"config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database"
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
"exportDatabase" = "Cadangkan"
"importDatabase" = "Pulihkan"
[pages.inbounds]
"title" = "Masuk"
"totalDownUp" = "Total Terkirim/Diterima"
"totalUsage" = "Penggunaan Total"
"inboundCount" = "Total Masuk"
"operate" = "Menu"
"enable" = "Aktifkan"
"remark" = "Catatan"
"protocol" = "Protokol"
"port" = "Port"
"traffic" = "Traffic"
"details" = "Rincian"
"transportConfig" = "Transport"
"expireDate" = "Durasi"
"resetTraffic" = "Reset Traffic"
"addInbound" = "Tambahkan Masuk"
"generalActions" = "Tindakan Umum"
"create" = "Buat"
"update" = "Perbarui"
"modifyInbound" = "Ubah Masuk"
"deleteInbound" = "Hapus Masuk"
"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?"
"deleteClient" = "Hapus Klien"
"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
"copyLink" = "Salin URL"
"address" = "Alamat"
"network" = "Jaringan"
"destinationPort" = "Port Tujuan"
"targetAddress" = "Alamat Target"
"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP"
"meansNoLimit" = " = Unlimited. (unit: GB)"
"totalFlow" = "Total Aliran"
"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa"
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
"certificatePath" = "Path Berkas"
"certificateContent" = "Konten Berkas"
"publicKeyPath" = "Path Kunci Publik"
"publicKeyContent" = "Konten Kunci Publik"
"keyPath" = "Path Kunci Privat"
"keyContent" = "Konten Kunci Privat"
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
"client" = "Klien"
"export" = "Ekspor Semua URL"
"clone" = "Duplikat"
"cloneInbound" = "Duplikat"
"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat."
"cloneInboundOk" = "Duplikat"
"resetAllTraffic" = "Reset Semua Traffic Masuk"
"resetAllTrafficTitle" = "Reset Semua Traffic Masuk"
"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?"
"resetInboundClientTraffics" = "Reset Traffic Klien Masuk"
"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk"
"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?"
"resetAllClientTraffics" = "Reset Traffic Semua Klien"
"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien"
"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?"
"delDepletedClients" = "Hapus Klien Habis"
"delDepletedClientsTitle" = "Hapus Klien Habis"
"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?"
"email" = "Email"
"emailDesc" = "Harap berikan alamat email yang unik."
"IPLimit" = "Batas IP"
"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)"
"IPLimitlog" = "Log IP"
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
"IPLimitlogclear" = "Hapus Log"
"setDefaultCert" = "Atur Sertifikat dari Panel"
"xtlsDesc" = "Xray harus versi 1.7.5"
"realityDesc" = "Xray harus versi 1.8.0+"
"telegramDesc" = "Harap berikan ID Telegram atau obrolan tanpa menggunakan '@'. (dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
"info" = "Info"
"same" = "Sama"
"inboundData" = "Data Masuk"
"exportInbound" = "Ekspor Masuk"
"import" = "Impor"
"importInbound" = "Impor Masuk"
[pages.client]
"add" = "Tambah Klien"
"edit" = "Edit Klien"
"submitAdd" = "Tambah Klien"
"submitEdit" = "Simpan Perubahan"
"clientCount" = "Jumlah Klien"
"bulk" = "Tambahkan Massal"
"method" = "Metode"
"first" = "Pertama"
"last" = "Terakhir"
"prefix" = "Awalan"
"postfix" = "Akhiran"
"delayedStart" = "Mulai saat Penggunaan Awal"
"expireDays" = "Durasi"
"days" = "Hari"
"renew" = "Perpanjang Otomatis"
"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
[pages.inbounds.toasts]
"obtain" = "Dapatkan"
[pages.inbounds.stream.general]
"request" = "Permintaan"
"response" = "Respons"
"name" = "Nama"
"value" = "Nilai"
[pages.inbounds.stream.tcp]
"version" = "Versi"
"method" = "Metode"
"path" = "Path"
"status" = "Status"
"statusDescription" = "Deskripsi Status"
"requestHeader" = "Header Permintaan"
"responseHeader" = "Header Respons"
[pages.inbounds.stream.quic]
"encryption" = "Enkripsi"
[pages.settings]
"title" = "Pengaturan Panel"
"save" = "Simpan"
"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan."
"restartPanel" = "Restart Panel"
"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server."
"actions" = "Tindakan"
"resetDefaultConfig" = "Reset ke Default"
"panelSettings" = "Umum"
"securitySettings" = "Otentikasi"
"TGBotSettings" = "Bot Telegram"
"panelListeningIP" = "IP Pendengar"
"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)"
"panelListeningDomain" = "Domain Pendengar"
"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)"
"panelPort" = "Port Pendengar"
"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)"
"publicKeyPath" = "Path Kunci Publik"
"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan /)"
"privateKeyPath" = "Path Kunci Privat"
"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan /)"
"panelUrlPath" = "URI Path"
"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan / dan diakhiri dengan /)"
"pageSize" = "Ukuran Halaman"
"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)"
"remarkModel" = "Model Catatan & Karakter Pemisah"
"datepicker" = "Jenis Kalender"
"datepickerPlaceholder" = "Pilih tanggal"
"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini."
"sampleRemark" = "Contoh Catatan"
"oldUsername" = "Username Saat Ini"
"currentPassword" = "Kata Sandi Saat Ini"
"newUsername" = "Username Baru"
"newPassword" = "Kata Sandi Baru"
"telegramBotEnable" = "Aktifkan Bot Telegram"
"telegramBotEnableDesc" = "Mengaktifkan bot Telegram."
"telegramToken" = "Token Telegram"
"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'."
"telegramProxy" = "Proxy SOCKS"
"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)"
"telegramChatId" = "ID Obrolan Admin"
"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
"telegramNotifyTime" = "Waktu Notifikasi"
"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)"
"tgNotifyBackup" = "Cadangan Database"
"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan."
"tgNotifyLogin" = "Notifikasi Login"
"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda."
"sessionMaxAge" = "Durasi Sesi"
"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)"
"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa"
"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)"
"trafficDiff" = "Notifikasi Batas Traffic"
"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)"
"tgNotifyCpu" = "Notifikasi Beban CPU"
"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)"
"timeZone" = "Zone Waktu"
"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini."
"subSettings" = "Langganan"
"subEnable" = "Aktifkan Layanan Langganan"
"subEnableDesc" = "Mengaktifkan layanan langganan."
"subListen" = "IP Pendengar"
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
"subPort" = "Port Pendengar"
"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)"
"subCertPath" = "Path Kunci Publik"
"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan /)"
"subKeyPath" = "Path Kunci Privat"
"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan /)"
"subPath" = "URI Path"
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan / dan diakhiri dengan /)"
"subDomain" = "Domain Pendengar"
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
"subUpdates" = "Interval Pembaruan"
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
"subEncrypt" = "Encode"
"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64."
"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."
"fragment" = "Fragmentasi"
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
[pages.xray]
"title" = "Konfigurasi Xray"
"save" = "Simpan"
"restart" = "Restart Xray"
"basicTemplate" = "Dasar"
"advancedTemplate" = "Lanjutan"
"generalConfigs" = "Strategi Umum"
"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum."
"logConfigs" = "Catatan"
"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan"
"blockConfigs" = "Pelindung"
"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta."
"blockCountryConfigs" = "Blokir Negara"
"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
"directCountryConfigs" = "Langsung ke Negara"
"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta."
"ipv4Configs" = "Pengalihan IPv4"
"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
"warpConfigs" = "Pengalihan WARP"
"warpConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP."
"Template" = "Template Konfigurasi Xray Lanjutan"
"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini."
"FreedomStrategy" = "Strategi Protokol Freedom"
"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom."
"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
"Torrent" = "Blokir Protokol BitTorrent"
"TorrentDesc" = "Memblokir protokol BitTorrent."
"PrivateIp" = "Blokir Koneksi ke IP Pribadi"
"PrivateIpDesc" = "Memblokir pembentukan koneksi ke rentang IP pribadi."
"Ads" = "Blokir Iklan"
"AdsDesc" = "Memblokir situs web periklanan."
"Family" = "Proteksi Keluarga"
"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya."
"Security" = "Pelindung Keamanan"
"SecurityDesc" = "Memblokir situs web malware, phishing, dan penambang kripto."
"Speedtest" = "Blokir Speedtest"
"SpeedtestDesc" = "Memblokir pembentukan koneksi ke situs web speedtest."
"IRIp" = "Blokir Koneksi ke IP Iran"
"IRIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Iran."
"IRDomain" = "Blokir Koneksi ke Domain Iran"
"IRDomainDesc" = "Memblokir pembentukan koneksi ke domain Iran."
"ChinaIp" = "Blokir Koneksi ke IP China"
"ChinaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP China."
"ChinaDomain" = "Blokir Koneksi ke Domain China"
"ChinaDomainDesc" = "Memblokir pembentukan koneksi ke domain China."
"RussiaIp" = "Blokir Koneksi ke IP Rusia"
"RussiaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Rusia."
"RussiaDomain" = "Blokir Koneksi ke Domain Rusia"
"RussiaDomainDesc" = "Memblokir pembentukan koneksi ke domain Rusia."
"VNIp" = "Blokir Koneksi ke IP Vietnam"
"VNIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Vietnam."
"VNDomain" = "Blokir Koneksi ke Domain Vietnam"
"VNDomainDesc" = "Memblokir pembentukan koneksi ke domain Vietnam."
"DirectIRIp" = "Koneksi Langsung ke IP Iran"
"DirectIRIpDesc" = "Membentuk koneksi langsung ke rentang IP Iran."
"DirectIRDomain" = "Koneksi Langsung ke Domain Iran"
"DirectIRDomainDesc" = "Membentuk koneksi langsung ke domain Iran."
"DirectChinaIp" = "Koneksi Langsung ke IP China"
"DirectChinaIpDesc" = "Membentuk koneksi langsung ke rentang IP China."
"DirectChinaDomain" = "Koneksi Langsung ke Domain China"
"DirectChinaDomainDesc" = "Membentuk koneksi langsung ke domain China."
"DirectRussiaIp" = "Koneksi Langsung ke IP Rusia"
"DirectRussiaIpDesc" = "Membentuk koneksi langsung ke rentang IP Rusia."
"DirectRussiaDomain" = "Koneksi Langsung ke Domain Rusia"
"DirectRussiaDomainDesc" = "Membentuk koneksi langsung ke domain Rusia."
"DirectVNIp" = "Koneksi Langsung ke IP Vietnam"
"DirectVNIpDesc" = "Membentuk koneksi langsung ke rentang IP Vietnam."
"DirectVNDomain" = "Koneksi Langsung ke Domain Vietnam"
"DirectVNDomainDesc" = "Membentuk koneksi langsung ke domain Vietnam."
"GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "Rute lalu lintas ke Google melalui IPv4."
"NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "Rute lalu lintas ke Netflix melalui IPv4."
"GoogleWARP" = "Google"
"GoogleWARPDesc" = "Tambahkan pengalihan untuk Google melalui WARP."
"OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
"NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP."
"MetaWARP" = "Meta"
"MetaWARPDesc" = "Merutekan lalu lintas ke Meta (Instagram, Facebook, WhatsApp, Threads,...) melalui WARP."
"AppleWARP" = "Apple"
"AppleWARPDesc" = "Merutekan lalu lintas ke Apple melalui WARP."
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "Merutekan lalu lintas ke Reddit melalui WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
"IRWARP" = "Domain Iran"
"IRWARPDesc" = "Rute lalu lintas ke domain Iran melalui WARP."
"Inbounds" = "Masuk"
"InboundsDesc" = "Menerima klien tertentu."
"Outbounds" = "Keluar"
"Balancers" = "Penyeimbang"
"OutboundsDesc" = "Atur jalur lalu lintas keluar."
"Routings" = "Aturan Pengalihan"
"RoutingsDesc" = "Prioritas setiap aturan penting!"
"completeTemplate" = "Semua"
"logLevel" = "Tingkat Log"
"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat."
"accessLog" = "Log Akses"
"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses"
"errorLog" = "Catatan eror"
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
[pages.xray.rules]
"first" = "Pertama"
"last" = "Terakhir"
"up" = "Naik"
"down" = "Turun"
"source" = "Sumber"
"dest" = "Tujuan"
"inbound" = "Masuk"
"outbound" = "Keluar"
"balancer" = "Pengimbang"
"info" = "Info"
"add" = "Tambahkan Aturan"
"edit" = "Edit Aturan"
"useComma" = "Item yang dipisahkan koma"
[pages.xray.outbound]
"addOutbound" = "Tambahkan Keluar"
"addReverse" = "Tambahkan Revers"
"editOutbound" = "Edit Keluar"
"editReverse" = "Edit Revers"
"tag" = "Tag"
"tagDesc" = "Tag Unik"
"address" = "Alamat"
"reverse" = "Revers"
"domain" = "Domain"
"type" = "Tipe"
"bridge" = "Jembatan"
"portal" = "Portal"
"intercon" = "Interkoneksi"
[pages.xray.balancer]
"addBalancer" = "Tambahkan Penyeimbang"
"editBalancer" = "Sunting Penyeimbang"
"balancerStrategy" = "Strategi"
"balancerSelectors" = "Penyeleksi"
"tag" = "Menandai"
"tagDesc" = "Label Unik"
"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi."
[pages.xray.wireguard]
"secretKey" = "Kunci Rahasia"
"publicKey" = "Kunci Publik"
"allowedIPs" = "IP yang Diizinkan"
"endpoint" = "Titik Akhir"
"psk" = "Kunci Pra-Bagi"
"domainStrategy" = "Strategi Domain"
[pages.xray.dns]
"enable" = "Aktifkan DNS"
"enableDesc" = "Aktifkan server DNS bawaan"
"strategy" = "Strategi Kueri"
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
"add" = "Tambahkan Server"
"edit" = "Sunting Server"
"domains" = "Domains"
[pages.xray.fakedns]
"add" = "Tambahkan DNS Palsu"
"edit" = "Edit DNS Palsu"
"ipPool" = "Subnet Kumpulan IP"
"poolSize" = "Ukuran Kolam"
[pages.settings.security]
"admin" = "Admin"
"secret" = "Token Rahasia"
"loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
"secretToken" = "Token Rahasia"
"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
[pages.settings.toasts]
"modifySettings" = "Ubah Pengaturan"
"getSettings" = "Dapatkan Pengaturan"
"modifyUser" = "Ubah Admin"
"originalUserPassIncorrect" = "Username atau password saat ini tidak valid"
"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong"
[tgbot]
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
"noResult" = "❗ Tidak ada hasil!"
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
"wentWrong" = "❌ Ada yang salah!"
"noIpRecord" = "❗ Tidak ada Catatan IP!"
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
"unlimited" = "♾ Tak terbatas"
"add" = "Tambah"
"month" = "Bulan"
"months" = "Bulan"
"day" = "Hari"
"days" = "Hari"
"hours" = "Jam"
"unknown" = "Tidak diketahui"
"inbounds" = "Masuk"
"clients" = "Klien"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
[tgbot.commands]
"unknown" = "❗ Perintah tidak dikenal."
"pleaseChoose" = "👇 Harap pilih:\r\n"
"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n"
"start" = "👋 Halo <i>{{ .Firstname }}</i>.\r\n"
"welcome" = "🤖 Selamat datang di <b>{{.Hostname }}</b> bot managemen.\r\n"
"status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!"
"getID" = "🆔 ID Anda:<code>{{.ID }}</code>"
"helpAdminCommands" = "Untuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari masuk (dengan statistik klien):\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%"
"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!"
"userSaved" = "✅ Pengguna Telegram tersimpan."
"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n"
"loginFailed" = "❗️ Gagal masuk ke panel.\r\n"
"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\r\n"
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
"time" = "⏰ Waktu: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"
"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n"
"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n"
"active" = "💡 Aktif: {{ .Enable }}\r\n"
"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n"
"online" = "🌐 Status Koneksi: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n"
"download" = "🔽 Unduh: ↓{{ .Download }}\r\n"
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n"
"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
"yes" = "✅ Ya"
"no" = "❌ Tidak"
[tgbot.buttons]
"closeKeyboard" = "❌ Tutup Papan Ketik"
"cancel" = "❌ Batal"
"cancelReset" = "❌ Batal Reset"
"cancelIpLimit" = "❌ Batal Batas IP"
"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?"
"confirmClearIps" = "✅ Konfirmasi Hapus IPs?"
"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?"
"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?"
"dbBackup" = "Dapatkan Cadangan DB"
"serverUsage" = "Penggunaan Server"
"getInbounds" = "Dapatkan Inbounds"
"depleteSoon" = "Habis Sebentar"
"clientUsage" = "Dapatkan Penggunaan"
"onlines" = "Klien Online"
"commands" = "Perintah"
"refresh" = "🔄 Perbarui"
"clearIPs" = "❌ Hapus IPs"
"removeTGUser" = "❌ Hapus Pengguna Telegram"
"selectTGUser" = "👤 Pilih Pengguna Telegram"
"selectOneTGUser" = "👤 Pilih Pengguna Telegram:"
"resetTraffic" = "📈 Reset Lalu Lintas"
"resetExpire" = "📅 Ubah Tanggal Kadaluarsa"
"ipLog" = "🔢 Log IP"
"ipLimit" = "🔢 Batas IP"
"setTGUser" = "👤 Set Pengguna Telegram"
"toggle" = "🔘 Aktifkan / Nonaktifkan"
"custom" = "🔢 Kustom"
"confirmNumber" = "✅ Konfirmasi: {{ .Num }}"
"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}"
"limitTraffic" = "🚧 Batas Lalu Lintas"
"getBanLogs" = "Dapatkan Log Pemblokiran"
[tgbot.answers]
"successfulOperation" = "✅ Operasi berhasil!"
"errorOperation" = "❗ Kesalahan dalam operasi."
"getInboundsFailed" = "❌ Gagal mendapatkan inbounds."
"canceled" = "❌ {{ .Email }}: Operasi dibatalkan."
"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil."
"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil."
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil."
"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil."
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil."
"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil."
"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil."
"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil."
"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP."
"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram."
"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil."
"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil."
"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil."
"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ID Telegram Anda dalam konfigurasi Anda.\r\n\r\nID Pengguna Anda: <code>{{ .TgUserID }}</code>"

View File

@@ -52,6 +52,9 @@
"secretToken" = "Секретный токен" "secretToken" = "Секретный токен"
"remained" = "остались" "remained" = "остались"
"security" = "Безопасность" "security" = "Безопасность"
"secAlertTitle" = "Предупреждение системы безопасности"
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
"secAlertConf" = "Некоторые конфигурации были определены как уязвимые для атак, что требует немедленных действий по усилению протоколов безопасности и защите от потенциальных нарушений безопасности."
[menu] [menu]
"dashboard" = "Статус системы" "dashboard" = "Статус системы"
@@ -62,7 +65,7 @@
"link" = "менеджмент" "link" = "менеджмент"
[pages.login] [pages.login]
"title" = "Логин" "title" = "Добро пожаловать"
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова" "loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
[pages.login.toasts] [pages.login.toasts]
@@ -76,10 +79,10 @@
"title" = "Статус системы" "title" = "Статус системы"
"memory" = "Память" "memory" = "Память"
"hard" = "Жесткий диск" "hard" = "Жесткий диск"
"xrayStatus" = "Статус" "xrayStatus" = "Xray"
"stopXray" = "Остановить Xray" "stopXray" = "Остановить"
"restartXray" = "Перезапустить Xray" "restartXray" = "Перезапустить"
"xraySwitch" = "Переключить версию" "xraySwitch" = "Версия"
"xraySwitchClick" = "Выберите желаемую версию" "xraySwitchClick" = "Выберите желаемую версию"
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями" "xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
"operationHours" = "Время работы системы" "operationHours" = "Время работы системы"
@@ -134,7 +137,7 @@
"destinationPort" = "Порт назначения" "destinationPort" = "Порт назначения"
"targetAddress" = "Целевой адрес" "targetAddress" = "Целевой адрес"
"monitorDesc" = "Оставьте пустым по умолчанию" "monitorDesc" = "Оставьте пустым по умолчанию"
"meansNoLimit" = "Значит без ограничений" "meansNoLimit" = " = Без ограничений (значение: ГБ)"
"totalFlow" = "Общий расход" "totalFlow" = "Общий расход"
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало" "leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию" "noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
@@ -173,12 +176,12 @@
"setDefaultCert" = "Установить сертификат с панели" "setDefaultCert" = "Установить сертификат с панели"
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5" "xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0" "realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)" "telegramDesc" = "Используйте только ID чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций" "subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
"info" = "Информация" "info" = "Информация"
"same" = "Тот же" "same" = "Тот же"
"inboundData" = "Входящие данные" "inboundData" = "Входящие данные"
"copyToClipboard" = "Копировать в буфер обмена" "exportInbound" = "Экспорт входящих"
"import" = "Импортировать" "import" = "Импортировать"
"importInbound" = "Импортировать входящее сообщение" "importInbound" = "Импортировать входящее сообщение"
@@ -195,26 +198,27 @@
"prefix" = "Префикс" "prefix" = "Префикс"
"postfix" = "Постфикс" "postfix" = "Постфикс"
"delayedStart" = "Начать с момента первого подключения" "delayedStart" = "Начать с момента первого подключения"
"expireDays" = "Срок действия" "expireDays" = "Длительность"
"days" = "дней" "days" = "дней"
"renew" = "Автопродление" "renew" = "Автопродление"
"renewDesc" = "Автоматическое продление через несколько дней после истечения срока действия. 0 = отключить" "renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Получить" "obtain" = "Получить"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "Заголовок запроса" "request" = "Запрос"
"response" = "Ответ"
"name" = "Имя" "name" = "Имя"
"value" = "Значение" "value" = "Ценить"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "Версия запроса" "version" = "Версия"
"requestMethod" = "Метод запроса" "method" = "Метод"
"requestPath" = "Путь запроса" "path" = "Путь"
"responseVersion" = "Версия ответа" "status" = "Положение дел"
"responseStatus" = "Статус ответа" "statusDescription" = "Описание статуса"
"responseStatusDescription" = "Описание статуса ответа" "requestHeader" = "Заголовок запроса"
"responseHeader" = "Заголовок ответа" "responseHeader" = "Заголовок ответа"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
@@ -246,6 +250,9 @@
"pageSize" = "Размер нумерации страниц" "pageSize" = "Размер нумерации страниц"
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить" "pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
"remarkModel" = "Модель примечания и символ разделения" "remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "выбор даты"
"datepickerPlaceholder" = "Выберите дату"
"datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия."
"sampleRemark" = "Пример замечания" "sampleRemark" = "Пример замечания"
"oldUsername" = "Текущее имя пользователя" "oldUsername" = "Текущее имя пользователя"
"currentPassword" = "Текущий пароль" "currentPassword" = "Текущий пароль"
@@ -255,6 +262,8 @@
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота" "telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота"
"telegramToken" = "Токен Telegram бота" "telegramToken" = "Токен Telegram бота"
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" "telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
"telegramProxy" = "Прокси Socks5"
"telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5. Настройте его параметры согласно руководству."
"telegramChatId" = "Telegram ID админа бота" "telegramChatId" = "Telegram ID админа бота"
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте." "telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
"telegramNotifyTime" = "Частота уведомлений бота Telegram" "telegramNotifyTime" = "Частота уведомлений бота Telegram"
@@ -296,6 +305,8 @@
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации" "subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
"subURI" = "URI обратного прокси" "subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" "subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"fragment" = "Фрагментация"
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
[pages.xray] [pages.xray]
"title" = "Настройки Xray" "title" = "Настройки Xray"
@@ -305,6 +316,8 @@
"advancedTemplate" = "Расширенный шаблон" "advancedTemplate" = "Расширенный шаблон"
"generalConfigs" = "Основные настройки" "generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки" "generalConfigsDesc" = "Эти параметры описывают общие настройки"
"logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
"blockConfigs" = "Блокировка конфигураций" "blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам" "blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"blockCountryConfigs" = "Конфигурации блокировки страны" "blockCountryConfigs" = "Конфигурации блокировки страны"
@@ -327,8 +340,10 @@
"PrivateIpDesc" = "Изменение шаблона конфигурации для предупреждения подключения к диапазонам частных IP-адресов" "PrivateIpDesc" = "Изменение шаблона конфигурации для предупреждения подключения к диапазонам частных IP-адресов"
"Ads" = "Блокировка рекламы" "Ads" = "Блокировка рекламы"
"AdsDesc" = "Изменение конфигурации для блокировки рекламы" "AdsDesc" = "Изменение конфигурации для блокировки рекламы"
"Family" = "Блокировать вредоносное ПО и контент для взрослых" "Family" = "Блокируйте вредоносное ПО и контент для взрослых"
"FamilyDesc" = "Резольверы DNS для блокировки вредоносных программ и контента для взрослых для защиты семьи" "FamilyDesc" = "DNS-преобразователи Cloudflare для блокировки вредоносного ПО и контента для взрослых в целях защиты семьи."
"Security" = "Блокируйте вредоносное ПО, фишинговые сайты и сайты криптомайнеров"
"SecurityDesc" = "Изменение шаблона конфигурации для защиты безопасности."
"Speedtest" = "Блокировать сайты для проверки скорости" "Speedtest" = "Блокировать сайты для проверки скорости"
"SpeedtestDesc" = "Изменение шаблона конфигурации для предупреждения подключения к веб-сайтам для тестирования скорости" "SpeedtestDesc" = "Изменение шаблона конфигурации для предупреждения подключения к веб-сайтам для тестирования скорости"
"IRIp" = "Заблокировать подключения к диапазонам IP-адресов Ирана" "IRIp" = "Заблокировать подключения к диапазонам IP-адресов Ирана"
@@ -367,23 +382,36 @@
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4" "GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
"NetflixIPv4" = "Использовать IPv4 для Netflix" "NetflixIPv4" = "Использовать IPv4 для Netflix"
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4" "NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
"GoogleWARP" = "Маршрутизация Google через WARP" "GoogleWARP" = "Google"
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP" "GoogleWARPDesc" = "Направляет трафик в Google через WARP."
"OpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP" "OpenAIWARP" = "OpenAI (ChatGPT)"
"OpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP" "OpenAIWARPDesc" = "Направляет трафик в OpenAI (ChatGPT) через WARP."
"NetflixWARP" = "Маршрутизация Netflix через WARP" "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP" "NetflixWARPDesc" = "Направляет трафик в Apple через WARP."
"SpotifyWARP" = "Маршрутизация Spotify через WARP" "MetaWARP" = "Мета"
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP" "MetaWARPDesc" = "Направляет трафик в Meta (Instagram, Facebook, WhatsApp, Threads...) через WARP."
"AppleWARP" = "Apple"
"AppleWARPDesc" = "Направляет трафик в Apple через WARP."
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "Направляет трафик в Reddit через WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Направляет трафик в Spotify через WARP."
"IRWARP" = "Маршрутизация доменов Ирана через WARP" "IRWARP" = "Маршрутизация доменов Ирана через WARP"
"IRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP" "IRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP"
"Inbounds" = "Входящие" "Inbounds" = "Входящие"
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей" "InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"Outbounds" = "Исходящие" "Outbounds" = "Исходящие"
"Balancers" = "Балансиры"
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера" "OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"Routings" = "Правила маршрутизации" "Routings" = "Правила маршрутизации"
"RoutingsDesc" = "Важен приоритет каждого правила!" "RoutingsDesc" = "Важен приоритет каждого правила!"
"completeTemplate" = "Все" "completeTemplate" = "Все"
"logLevel" = "Уровень журнала"
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
"accessLog" = "Журнал доступа"
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
"errorLog" = "Журнал ошибок"
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
[pages.xray.rules] [pages.xray.rules]
"first" = "Первый" "first" = "Первый"
@@ -394,6 +422,7 @@
"dest" = "Пункт назначения" "dest" = "Пункт назначения"
"inbound" = "Входящий" "inbound" = "Входящий"
"outbound" = "Исходящий" "outbound" = "Исходящий"
"balancer" = "балансир"
"info" = "Информация" "info" = "Информация"
"add" = "Добавить правило" "add" = "Добавить правило"
"edit" = "Редактировать правило" "edit" = "Редактировать правило"
@@ -414,6 +443,38 @@
"portal" = "Портал" "portal" = "Портал"
"intercon" = "Соединение" "intercon" = "Соединение"
[pages.xray.balancer]
"addBalancer" = "Добавить балансир"
"editBalancer" = "Редактировать балансир"
"balancerStrategy" = "Стратегия"
"balancerSelectors" = "Селекторы"
"tag" = "Тег"
"tagDesc" = "уникальный тег"
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ"
"allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка"
"psk" = "Общий ключ"
"domainStrategy" = "Стратегия домена"
[pages.xray.dns]
"enable" = "Включить DNS"
"enableDesc" = "Включить встроенный DNS-сервер"
"strategy" = "Стратегия запроса"
"strategyDesc" = "Общая стратегия разрешения доменных имен"
"add" = "Добавить сервер"
"edit" = "Редактировать сервер"
"domains" = "Домены"
[pages.xray.fakedns]
"add" = "Добавить поддельный DNS"
"edit" = "Редактировать поддельный DNS"
"ipPool" = "Подсеть пула IP"
"poolSize" = "Размер пула"
[pages.settings.security] [pages.settings.security]
"admin" = "Админ" "admin" = "Админ"
"secret" = "Секретный токен" "secret" = "Секретный токен"
@@ -437,6 +498,7 @@
"noIpRecord" = "❗ Нет записей об IP-адресе!" "noIpRecord" = "❗ Нет записей об IP-адресе!"
"noInbounds" = "❗ Входящих соединений не найдено!" "noInbounds" = "❗ Входящих соединений не найдено!"
"unlimited" = "♾ Неограниченно" "unlimited" = "♾ Неограниченно"
"add" = "Добавить"
"month" = "Месяц" "month" = "Месяц"
"months" = "Месяцев" "months" = "Месяцев"
"day" = "День" "day" = "День"
@@ -445,6 +507,8 @@
"unknown" = "Неизвестно" "unknown" = "Неизвестно"
"inbounds" = "Входящие" "inbounds" = "Входящие"
"clients" = "Клиенты" "clients" = "Клиенты"
"offline" = "🔴 Офлайн"
"online" = "🟢 Онлайн"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ Неизвестная команда" "unknown" = "❗ Неизвестная команда"
@@ -455,8 +519,8 @@
"status" = "✅ Бот работает нормально!" "status" = "✅ Бот работает нормально!"
"usage" = "❗ Пожалуйста, укажите текст для поиска!" "usage" = "❗ Пожалуйста, укажите текст для поиска!"
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>" "getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n \r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>" "helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n\r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nИспользуйте UUID для vmess/vless и пароль для Trojan." "helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%" "cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%"
@@ -471,7 +535,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IP-адреса: \r\n{{ .IPs }}\r\n" "ips" = "🔢 IP-адреса:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Загрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 Загрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Память сервера: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 Память сервера: {{ .Current }}/{{ .Total }}\r\n"
@@ -485,8 +549,9 @@
"port" = "🔌 Порт: {{ .Port }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n"
"expire" = "📅 Дата окончания: {{ .Time }}\r\n" "expire" = "📅 Дата окончания: {{ .Time }}\r\n"
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n" "expireIn" = "📅 Окончание через: {{ .Time }}\r\n"
"active" = "💡 Активен: ✅ Да\r\n" "active" = "💡 Активен: {{ .Enable }}\r\n"
"inactive" = "💡 Активен: ❌ Нет\r\n" "enabled" = "🚨 Включен: {{ .Enable }}\r\n"
"online" = "🌐 Статус соединения: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n" "upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n"
"download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n" "download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n"
@@ -494,10 +559,13 @@
"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n" "TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n" "exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n" "exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Клиентов онлайн: {{ .Count }}\r\n"
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n" "disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n" "backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n"
"yes" = "✅ Да"
"no" = "❌ Нет"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Закрыть клавиатуру" "closeKeyboard" = "❌ Закрыть клавиатуру"
@@ -507,11 +575,13 @@
"confirmResetTraffic" = "✅ Подтвердить сброс трафика?" "confirmResetTraffic" = "✅ Подтвердить сброс трафика?"
"confirmClearIps" = "✅ Подтвердить очистку IP?" "confirmClearIps" = "✅ Подтвердить очистку IP?"
"confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?" "confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?"
"confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?"
"dbBackup" = "Получить резервную копию DB" "dbBackup" = "Получить резервную копию DB"
"serverUsage" = "Использование сервера" "serverUsage" = "Использование сервера"
"getInbounds" = "Получить входящие потоки" "getInbounds" = "Получить входящие потоки"
"depleteSoon" = "Скоро исчерпание" "depleteSoon" = "Скоро исчерпание"
"clientUsage" = "Получить использование" "clientUsage" = "Получить использование"
"onlines" = "Онлайн-клиенты"
"commands" = "Команды" "commands" = "Команды"
"refresh" = "🔄 Обновить" "refresh" = "🔄 Обновить"
"clearIPs" = "❌ Очистить IP" "clearIPs" = "❌ Очистить IP"
@@ -519,14 +589,16 @@
"selectTGUser" = "👤 Выбрать пользователя Telegram" "selectTGUser" = "👤 Выбрать пользователя Telegram"
"selectOneTGUser" = "👤 Выберите пользователя Telegram:" "selectOneTGUser" = "👤 Выберите пользователя Telegram:"
"resetTraffic" = "📈 Сбросить трафик" "resetTraffic" = "📈 Сбросить трафик"
"resetExpire" = "📅 Сбросить дату окончания" "resetExpire" = "📅 Изменить дату окончания"
"ipLog" = "🔢 Лог IP" "ipLog" = "🔢 Лог IP"
"ipLimit" = "🔢 Лимит IP" "ipLimit" = "🔢 Лимит IP"
"setTGUser" = "👤 Установить пользователя Telegram" "setTGUser" = "👤 Установить пользователя Telegram"
"toggle" = "🔘 Вкл./Выкл." "toggle" = "🔘 Вкл./Выкл."
"custom" = "🔢 Обычай" "custom" = "🔢 Свой"
"confirmNumber" = "✅ Подтвердить : {{ .Num }}" "confirmNumber" = "✅ Подтвердить: {{ .Num }}"
"confirmNumberAdd" = "✅ Подтвердить добавление: {{ .Num }}"
"limitTraffic" = "🚧 Лимит трафика" "limitTraffic" = "🚧 Лимит трафика"
"getBanLogs" = "Логи блокировок"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Успешный!" "successfulOperation" = "✅ Успешный!"
@@ -546,5 +618,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален." "removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален."
"enableSuccess" = "✅ {{ .Email }}: Включено успешно." "enableSuccess" = "✅ {{ .Email }}: Включено успешно."
"disableSuccess" = "✅ {{ .Email }}: Отключено успешно." "disableSuccess" = "✅ {{ .Email }}: Отключено успешно."
"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>" "askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <code>{{ .TgUserID }}</code>"
"askToAddUserName" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваше имя пользователя или идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>"

View File

@@ -20,7 +20,7 @@
"check" = "Kiểm tra" "check" = "Kiểm tra"
"indefinite" = "Không xác định" "indefinite" = "Không xác định"
"unlimited" = "Không giới hạn" "unlimited" = "Không giới hạn"
"none" = "Không có" "none" = "None"
"qrCode" = "Mã QR" "qrCode" = "Mã QR"
"info" = "Thông tin thêm" "info" = "Thông tin thêm"
"edit" = "Chỉnh sửa" "edit" = "Chỉnh sửa"
@@ -32,7 +32,7 @@
"transmission" = "Truyền tải" "transmission" = "Truyền tải"
"host" = "Máy chủ" "host" = "Máy chủ"
"path" = "Đường dẫn" "path" = "Đường dẫn"
"camouflage" = "camouflage" "camouflage" = "Ngụy trang"
"status" = "Trạng thái" "status" = "Trạng thái"
"enabled" = "Đã kích hoạt" "enabled" = "Đã kích hoạt"
"disabled" = "Đã tắt" "disabled" = "Đã tắt"
@@ -52,17 +52,20 @@
"secretToken" = "Mã bí mật" "secretToken" = "Mã bí mật"
"remained" = "Còn lại" "remained" = "Còn lại"
"security" = "Bảo vệ" "security" = "Bảo vệ"
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn"
"secAlertConf" = "Một số cấu hình nhất định đã được xác định là dễ bị tấn công, thúc đẩy hành động ngay lập tức để củng cố các giao thức bảo mật và bảo vệ chống lại các vi phạm bảo mật tiềm ẩn."
[menu] [menu]
"dashboard" = "Trạng thái hệ thống" "dashboard" = "Trạng thái hệ thống"
"inbounds" = "Inbounds" "inbounds" = "Đầu vào khách hàng"
"settings" = "Cài đặt bảng điều khiển" "settings" = "Cài đặt bảng điều khiển"
"logout" = "Đăng xuất" "logout" = "Đăng xuất"
"xray" = "Xray Cài đặt" "xray" = "Cài đặt Xray"
"link" = "sự quản lý" "link" = "Quản lý"
[pages.login] [pages.login]
"title" = "Đăng nhập" "title" = "Chào mừng"
"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại." "loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại."
[pages.login.toasts] [pages.login.toasts]
@@ -74,22 +77,22 @@
[pages.index] [pages.index]
"title" = "Trạng thái hệ thống" "title" = "Trạng thái hệ thống"
"memory" = "Bộ nhớ" "memory" = "Ram"
"hard" = "Ổ cứng" "hard" = "Dung lượng"
"xrayStatus" = "Trạng thái của Xray" "xrayStatus" = "Xray"
"stopXray" = "Dừng Xray" "stopXray" = "Dừng lại"
"restartXray" = "Khởi động lại Xray" "restartXray" = "Khởi động lại"
"xraySwitch" = "Chuyển đổi phiên bản" "xraySwitch" = "Phiên bản"
"xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang." "xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang."
"xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại." "xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại."
"operationHours" = "Thời gian hoạt động" "operationHours" = "Thời gian hoạt động"
"systemLoad" = "Tải hệ thống" "systemLoad" = "Tải hệ thống"
"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua" "systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua"
"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các card mạng." "connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các thẻ mạng."
"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các card mạng." "connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các thẻ mạng."
"connectionCount" = "Số lượng kết nối" "connectionCount" = "Số lượng kết nối"
"upSpeed" = "Tổng tốc độ tải lên cho tất cả các card mạng." "upSpeed" = "Tổng tốc độ tải lên cho tất cả các thẻ mạng."
"downSpeed" = "Tổng tốc độ tải xuống cho tất cả các card mạng." "downSpeed" = "Tổng tốc độ tải xuống cho tất cả các thẻ mạng."
"totalSent" = "Tổng lưu lượng tải lên của tất cả các thẻ mạng kể từ khi hệ thống khởi động." "totalSent" = "Tổng lưu lượng tải lên của tất cả các thẻ mạng kể từ khi hệ thống khởi động."
"totalReceive" = "Tổng lưu lượng tải xuống trên tất cả các thẻ mạng kể từ khi hệ thống khởi động." "totalReceive" = "Tổng lưu lượng tải xuống trên tất cả các thẻ mạng kể từ khi hệ thống khởi động."
"xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray" "xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray"
@@ -125,8 +128,8 @@
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)" "modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
"deleteInbound" = "Xóa điểm vào (Inbound)" "deleteInbound" = "Xóa điểm vào (Inbound)"
"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)" "deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)"
"deleteClient" = "Xóa khách hàng" "deleteClient" = "Xóa người dùng"
"deleteClientContent" = "Bạn có chắc chắn muốn xóa ng dng khách không?" "deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?"
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?" "resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
"copyLink" = "Sao chép liên kết" "copyLink" = "Sao chép liên kết"
"address" = "Địa chỉ" "address" = "Địa chỉ"
@@ -134,7 +137,7 @@
"destinationPort" = "Cổng đích" "destinationPort" = "Cổng đích"
"targetAddress" = "Địa chỉ mục tiêu" "targetAddress" = "Địa chỉ mục tiêu"
"monitorDesc" = "Mặc định để trống" "monitorDesc" = "Mặc định để trống"
"meansNoLimit" = "Nghĩa là không giới hạn" "meansNoLimit" = " = Không giới hạn (đơn vị: GB)"
"totalFlow" = "Tổng lưu lượng" "totalFlow" = "Tổng lưu lượng"
"leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn" "leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn"
"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định" "noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định"
@@ -145,24 +148,24 @@
"keyPath" = "Đường dẫn khóa riêng tư" "keyPath" = "Đường dẫn khóa riêng tư"
"keyContent" = "Nội dung khóa riêng tư" "keyContent" = "Nội dung khóa riêng tư"
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép" "clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
"client" = "Client" "client" = "Người dùng"
"export" = "Xuất liên kết" "export" = "Xuất liên kết"
"clone" = "Sao chép" "clone" = "Sao chép"
"cloneInbound" = "Sao chép điểm vào (Inbound)" "cloneInbound" = "Sao chép điểm vào (Inbound)"
"cloneInboundContent" = "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và Clients, sẽ được áp dụng cho bản sao." "cloneInboundContent" = "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và máy khách, sẽ được áp dụng cho bản sao."
"cloneInboundOk" = "Sao chép" "cloneInboundOk" = "Sao chép"
"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào" "resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào" "resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào"
"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?" "resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?"
"resetInboundClientTraffics" = "Đặt lại lưu lượng cho các client của điểm vào" "resetInboundClientTraffics" = "Đặt lại lưu lượng toàn bộ người dùng của điểm vào"
"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả lưu lượng của client" "resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào"
"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các client của điểm vào này không?" "resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?"
"resetAllClientTraffics" = "Đặt lại lưu lượng cho tất cả client" "resetAllClientTraffics" = "Đặt lại lưu lượng cho toàn bộ người dùng"
"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho tất cả client" "resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng"
"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho tất cả client không?" "resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?"
"delDepletedClients" = "Xóa các client đã cạn kiệt" "delDepletedClients" = "Xóa các người dùng đã cạn kiệt"
"delDepletedClientsTitle" = "Xóa các client đã cạn kiệt" "delDepletedClientsTitle" = "Xóa các người dùng đã cạn kiệt"
"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa tất cả các client đã cạn kiệt không?" "delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất." "emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất."
"IPLimit" = "Giới hạn IP" "IPLimit" = "Giới hạn IP"
@@ -173,21 +176,21 @@
"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển" "setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển"
"xtlsDesc" = "Xray core cần phiên bản 1.7.5" "xtlsDesc" = "Xray core cần phiên bản 1.7.5"
"realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn." "realityDesc" = "Xray core cần phiên bản 1.8.0 hoặc cao hơn."
"telegramDesc" = "Sử dụng Telegram ID mà không cần ký hiệu @ hoặc chat IDs (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)" "telegramDesc" = "Chỉ sử dụng ID trò chuyện (bạn có thể nhận được nó ở đây @userinfobot hoặc sử dụng lệnh '/id' trong bot)"
"subscriptionDesc" = "Bạn có thể tìm liên kết đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau" "subscriptionDesc" = "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau"
"info" = "Thông tin" "info" = "Thông tin"
"same" = "Giống nhau" "same" = "Giống nhau"
"inboundData" = "Dữ liệu gửi đến" "inboundData" = "Dữ liệu gửi đến"
"copyToClipboard" = "Sao chép vào bảng nhớ tạm" "exportInbound" = "Xuất nhập khẩu"
"import" = "Nhập" "import" = "Nhập"
"importInbound" = "Nhập hàng gửi về" "importInbound" = "Nhập inbound"
[pages.client] [pages.client]
"add" = "Thêm Client" "add" = "Thêm người dùng"
"edit" = "Chỉnh sửa Client" "edit" = "Chỉnh sửa người dùng"
"submitAdd" = "Thêm Client" "submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi" "submitEdit" = "Lưu thay đổi"
"clientCount" = "Số lượng Client" "clientCount" = "Số lượng người dùng"
"bulk" = "Thêm hàng loạt" "bulk" = "Thêm hàng loạt"
"method" = "Phương pháp" "method" = "Phương pháp"
"first" = "Đầu tiên" "first" = "Đầu tiên"
@@ -195,27 +198,28 @@
"prefix" = "Tiền tố" "prefix" = "Tiền tố"
"postfix" = "Hậu tố" "postfix" = "Hậu tố"
"delayedStart" = "Bắt đầu sau khi sử dụng lần đầu" "delayedStart" = "Bắt đầu sau khi sử dụng lần đầu"
"expireDays" = "Số ngày hết hạn" "expireDays" = "Khoảng thời gian"
"days" = "ngày" "days" = "ngày"
"renew" = "Tự động gia hạn" "renew" = "Tự động gia hạn"
"renewDesc" = "Tự động gia hạn những ngày sau khi hết hạn. 0 = tắt" "renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "Nhận" "obtain" = "Nhận"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "Tiêu đề yêu cầu" "request" = "Lời yêu cầu"
"response" = "Phản ứng"
"name" = "Tên" "name" = "Tên"
"value" = "Giá trị" "value" = "Giá trị"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "Phiên bản yêu cầu" "version" = "Phiên bản"
"requestMethod" = "Phương thức yêu cầu" "method" = "Phương pháp"
"requestPath" = "Đường dẫn yêu cầu" "path" = "Đường dẫn"
"responseVersion" = "Phiên bản phản hồi" "status" = "Trạng thái"
"responseStatus" = "Trạng thái phản hồi" "statusDescription" = "Tình trạng Mô tả"
"responseStatusDescription" = "Mô tả trạng thái phản hồi" "requestHeader" = "Header yêu cầu"
"responseHeader" = "Tiêu đề phản hồi" "responseHeader" = "Header phản hồi"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
"encryption" = "Mã hóa" "encryption" = "Mã hóa"
@@ -224,28 +228,31 @@
"title" = "Cài đặt" "title" = "Cài đặt"
"save" = "Lưu" "save" = "Lưu"
"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi." "infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi."
"restartPanel" = "Khởi động lại Bảng điều khiển" "restartPanel" = "Khởi động lại bảng điều khiển"
"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ." "restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ."
"actions" = "Hành động" "actions" = "Hành động"
"resetDefaultConfig" = "Đặt lại Cấu hình Mặc định" "resetDefaultConfig" = "Đặt lại cấu hình mặc định"
"panelSettings" = "Cài đặt Bảng điều khiển" "panelSettings" = "Bảng điều khiển"
"securitySettings" = "Cài đặt Bảo mật" "securitySettings" = "Bảo mật"
"TGBotSettings" = "Cài đặt Bot Telegram" "TGBotSettings" = "Bot Telegram"
"panelListeningIP" = "IP Nghe của Bảng điều khiển" "panelListeningIP" = "IP Nghe của bảng điều khiển"
"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP." "panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP."
"panelListeningDomain" = "Tên miền của nghe Bảng điều khiển" "panelListeningDomain" = "Tên miền của nghe bảng điều khiển"
"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" "panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
"panelPort" = "Cổng Bảng điều khiển" "panelPort" = "Cổng bảng điều khiển"
"panelPortDesc" = "Cổng được sử dụng để hiển thị bảng điều khiển này" "panelPortDesc" = "Cổng được sử dụng để kết nối với bảng điều khiển này"
"publicKeyPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Bảng điều khiển" "publicKeyPath" = "Đường dẫn file chứng chỉ bảng điều khiển"
"publicKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với." "publicKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
"privateKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Bảng điều khiển" "privateKeyPath" = "Đường dẫn file khóa của chứng chỉ bảng điều khiển"
"privateKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với." "privateKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')"
"panelUrlPath" = "Đường dẫn gốc URL Bảng điều khiển" "panelUrlPath" = "Đường dẫn gốc URL bảng điều khiển"
"panelUrlPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng." "panelUrlPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"pageSize" = "Kích thước phân trang" "pageSize" = "Kích thước phân trang"
"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt" "pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt"
"remarkModel" = "Ghi chú mô hình và ký tự phân tách" "remarkModel" = "Ghi chú mô hình và ký tự phân tách"
"datepicker" = "Kiểu lịch"
"datepickerPlaceholder" = "Chọn ngày"
"datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này."
"sampleRemark" = "Nhận xét mẫu" "sampleRemark" = "Nhận xét mẫu"
"oldUsername" = "Tên người dùng hiện tại" "oldUsername" = "Tên người dùng hiện tại"
"currentPassword" = "Mật khẩu hiện tại" "currentPassword" = "Mật khẩu hiện tại"
@@ -255,6 +262,8 @@
"telegramBotEnableDesc" = "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram" "telegramBotEnableDesc" = "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram"
"telegramToken" = "Token Telegram" "telegramToken" = "Token Telegram"
"telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather" "telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather"
"telegramProxy" = "Socks5 Proxy"
"telegramProxyDesc" = "Nếu bạn cần socks5 proxy để kết nối với Telegram. Điều chỉnh cài đặt của nó theo hướng dẫn."
"telegramChatId" = "Chat ID Telegram của quản trị viên" "telegramChatId" = "Chat ID Telegram của quản trị viên"
"telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn." "telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn."
"telegramNotifyTime" = "Thời gian thông báo của bot Telegram" "telegramNotifyTime" = "Thời gian thông báo của bot Telegram"
@@ -263,7 +272,7 @@
"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo." "tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo."
"tgNotifyLogin" = "Thông báo Đăng nhập" "tgNotifyLogin" = "Thông báo Đăng nhập"
"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn." "tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn."
"sessionMaxAge" = "Tuổi tối đa của phiên" "sessionMaxAge" = "Thời gian tối đa của phiên"
"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)" "sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)"
"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo" "expireTimeDiff" = "Ngưỡng hết hạn cho thông báo"
"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)" "expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)"
@@ -273,38 +282,42 @@
"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)" "tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)"
"timeZone" = "Múi giờ" "timeZone" = "Múi giờ"
"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này." "timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này."
"subSettings" = "Đăng ký" "subSettings" = "Gói đăng ký"
"subEnable" = "Bật dịch vụ" "subEnable" = "Bật dịch vụ"
"subEnableDesc" = "Tính năng đăng ký với cấu hình riêng" "subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
"subListen" = "Listening IP" "subListen" = "Listening IP"
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP" "subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
"subPort" = "Cổng Đăng ký" "subPort" = "Cổng gói đăng ký"
"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ" "subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ"
"subCertPath" = "Đường dẫn tập tin khóa công khai Chứng chỉ Đăng ký" "subCertPath" = "Đường dẫn file chứng chỉ gói đăng ký"
"subCertPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'" "subCertPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
"subKeyPath" = "Đường dẫn tập tin khóa riêng tư Chứng chỉ Đăng ký" "subKeyPath" = "Đường dẫn file khóa của chứng chỉ gói đăng ký"
"subKeyPathDesc" = "Điền vào đường dẫn tuyệt đối bắt đầu với '/'" "subKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')"
"subPath" = "Đường dẫn gốc URL Đăng ký" "subPath" = "Đường dẫn gốc URL gói đăng ký"
"subPathDesc" = "Phải bắt đầu bằng '/' và kết thúc bằng '/'" "subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"subDomain" = "Tên miền con" "subDomain" = "Tên miền con"
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" "subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
"subUpdates" = "Khoảng thời gian cập nhật đăng ký" "subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách" "subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
"subEncrypt" = "Mã hóa cấu hình" "subEncrypt" = "Mã hóa cấu hình"
"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong đăng ký" "subEncryptDesc" = "Mã hóa các cấu hình được trả về trong gói đăng ký"
"subShowInfo" = "Hiển thị thông tin sử dụng" "subShowInfo" = "Hiển thị thông tin sử dụng"
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" "subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
"subURI" = "URI proxy ngược" "subURI" = "URI proxy trung gian"
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy" "subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
"fragment" = "Sự phân mảnh"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
[pages.xray] [pages.xray]
"title" = "Xray Cài đặt" "title" = "Cài đặt Xray"
"save" = "Lưu cài đặt" "save" = "Lưu cài đặt"
"restart" = "Khởi động lại Xray" "restart" = "Khởi động lại Xray"
"basicTemplate" = "Mẫu Cơ bản" "basicTemplate" = "Mẫu Cơ bản"
"advancedTemplate" = "Mẫu Nâng cao" "advancedTemplate" = "Mẫu Nâng cao"
"generalConfigs" = "Cấu hình Chung" "generalConfigs" = "Cấu hình Chung"
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát." "generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
"logConfigs" = "Nhật ký"
"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần"
"blockConfigs" = "Cấu hình Chặn" "blockConfigs" = "Cấu hình Chặn"
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể." "blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia" "blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
@@ -327,8 +340,10 @@
"PrivateIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP riêng tư." "PrivateIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP riêng tư."
"Ads" = "Chặn Quảng cáo" "Ads" = "Chặn Quảng cáo"
"AdsDesc" = "Thay đổi mẫu cấu hình để chặn quảng cáo." "AdsDesc" = "Thay đổi mẫu cấu hình để chặn quảng cáo."
"Family" = "Chặn Phần mềm độc hại và Nội dung cho Người lớn" "Family" = "Chặn phần mềm độc hại và nội dung người lớn"
"FamilyDesc" = "Các trình giải quyết DNS để chặn phần mềm độc hại và nội dung cho bảo vệ gia đình." "FamilyDesc" = "Trình phân giải DNS của Cloudflare để chặn phần mềm độc hại và nội dung người lớn để bảo vệ gia đình."
"Security" = "Chặn các trang web chứa phần mềm độc hại, lừa đảo và khai thác tiền điện tử"
"SecurityDesc" = "Thay đổi mẫu cấu hình để bảo vệ Bảo mật."
"Speedtest" = "Chặn Trang web Speedtest" "Speedtest" = "Chặn Trang web Speedtest"
"SpeedtestDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các trang web Speedtest." "SpeedtestDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các trang web Speedtest."
"IRIp" = "Vô hiệu hóa kết nối đến dải IP của Iran" "IRIp" = "Vô hiệu hóa kết nối đến dải IP của Iran"
@@ -367,23 +382,36 @@
"GoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4." "GoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
"NetflixIPv4" = "Sử dụng IPv4 cho Netflix" "NetflixIPv4" = "Sử dụng IPv4 cho Netflix"
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4." "NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
"GoogleWARP" = "Định tuyến Google qua WARP." "GoogleWARP" = "Google"
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP." "GoogleWARPDesc" = "Định tuyến lưu lượng truy cập tới Google thông qua WARP."
"OpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP." "OpenAIWARP" = "OpenAI (ChatGPT)"
"OpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP." "OpenAIWARPDesc" = "Định tuyến lưu lượng truy cập tới OpenAI (ChatGPT) thông qua WARP."
"NetflixWARP" = "Định tuyến Netflix qua WARP." "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP." "NetflixWARPDesc" = "Định tuyến lưu lượng truy cập tới Netflix thông qua WARP."
"SpotifyWARP" = "Định tuyến Spotify qua WARP." "MetaWARP" = "Meta"
"SpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP." "MetaWARPDesc" = "Định tuyến lưu lượng truy cập tới Meta (Instagram, Facebook, WhatsApp, Threads,...) thông qua WARP."
"AppleWARP" = "Apple"
"AppleWARPDesc" = "Định tuyến lưu lượng truy cập tới Apple thông qua WARP."
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "Định tuyến lưu lượng truy cập tới Reddit thông qua WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Định tuyến lưu lượng truy cập tới Spotify thông qua WARP."
"IRWARP" = "Định tuyến tên miền của Iran qua WARP." "IRWARP" = "Định tuyến tên miền của Iran qua WARP."
"IRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP." "IRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP."
"Inbounds" = "Đầu vào" "Inbounds" = "Đầu vào"
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể." "InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
"Outbounds" = "Đầu ra" "Outbounds" = "Đầu ra"
"Balancers" = "Cân bằng"
"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này." "OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
"Routings" = "Quy tắc định tuyến" "Routings" = "Quy tắc định tuyến"
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!" "RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
"completeTemplate" = "All" "completeTemplate" = "All"
"logLevel" = "Mức đăng nhập"
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
"accessLog" = "Nhật ký truy cập"
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
"errorLog" = "Nhật ký lỗi"
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
[pages.xray.rules] [pages.xray.rules]
"first" = "Đầu tiên" "first" = "Đầu tiên"
@@ -394,6 +422,7 @@
"dest" = "Đích" "dest" = "Đích"
"inbound" = "Vào" "inbound" = "Vào"
"outbound" = "Ra" "outbound" = "Ra"
"balancer" = "Cân bằng"
"info" = "Thông tin" "info" = "Thông tin"
"add" = "Thêm quy tắc" "add" = "Thêm quy tắc"
"edit" = "Chỉnh sửa quy tắc" "edit" = "Chỉnh sửa quy tắc"
@@ -414,13 +443,45 @@
"portal" = "Cổng thông tin" "portal" = "Cổng thông tin"
"intercon" = "Kết nối" "intercon" = "Kết nối"
[pages.xray.balancer]
"addBalancer" = "Thêm cân bằng"
"editBalancer" = "Chỉnh sửa cân bằng"
"balancerStrategy" = "Chiến lược"
"balancerSelectors" = "Bộ chọn"
"tag" = "Thẻ"
"tagDesc" = "thẻ duy nhất"
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
[pages.xray.wireguard]
"secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai"
"allowedIPs" = "IP được phép"
"endpoint" = "Điểm cuối"
"psk" = "Khóa chia sẻ"
"domainStrategy" = "Chiến lược tên miền"
[pages.xray.dns]
"enable" = "Kích hoạt DNS"
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
"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ủ"
"edit" = "Chỉnh sửa máy chủ"
"domains" = "Tên miền"
[pages.xray.fakedns]
"add" = "Thêm DNS giả"
"edit" = "Chỉnh sửa DNS giả"
"ipPool" = "Mạng con nhóm IP"
"poolSize" = "Kích thước bể bơi"
[pages.settings.security] [pages.settings.security]
"admin" = "Quản trị viên" "admin" = "Quản trị viên"
"secret" = "Mã thông báo bí mật" "secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập" "loginSecurity" = "Bảo mật đăng nhập"
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng" "loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
"secretToken" = "Mã thông báo bí mật" "secretToken" = "Mã bí mật"
"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã thông báo này một cách an toàn ở nơi an toàn. Mã thông báo này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui." "secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Chỉnh sửa cài đặt " "modifySettings" = "Chỉnh sửa cài đặt "
@@ -437,6 +498,7 @@
"noIpRecord" = "❗ Không có bản ghi IP!" "noIpRecord" = "❗ Không có bản ghi IP!"
"noInbounds" = "❗ Không tìm thấy inbound!" "noInbounds" = "❗ Không tìm thấy inbound!"
"unlimited" = "♾ Không giới hạn" "unlimited" = "♾ Không giới hạn"
"add" = "Thêm"
"month" = "Tháng" "month" = "Tháng"
"months" = "Tháng" "months" = "Tháng"
"day" = "Ngày" "day" = "Ngày"
@@ -444,7 +506,9 @@
"hours" = "Giờ" "hours" = "Giờ"
"unknown" = "Không rõ" "unknown" = "Không rõ"
"inbounds" = "Vào" "inbounds" = "Vào"
"clients" = "Các khách hàng" "clients" = "Các người dùng"
"offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ Lệnh không rõ" "unknown" = "❗ Lệnh không rõ"
@@ -455,8 +519,8 @@
"status" = "✅ Bot hoạt động bình thường!" "status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!" "usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>" "getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
"helpAdminCommands" = "Tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n \r\nTìm kiếm inbounds (với thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>" "helpAdminCommands" = "Tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nTìm kiếm inbounds (với thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>"
"helpClientCommands" = "Để tìm kiếm thống kê, hãy sử dụng lệnh sau:\r\n \r\n<code>/usage [UUID|Mật khẩu]</code>\r\n \r\nSử dụng UUID cho vmess/vless và Mật khẩu cho Trojan." "helpClientCommands" = "Để tìm kiếm thống kê, hãy sử dụng lệnh sau:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%" "cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%"
@@ -471,7 +535,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 Các IP: \r\n{{ .IPs }}\r\n" "ips" = "🔢 Các IP:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n" "serverUpTime" = "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n" "serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n"
@@ -485,8 +549,9 @@
"port" = "🔌 Cổng: {{ .Port }}\r\n" "port" = "🔌 Cổng: {{ .Port }}\r\n"
"expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n" "expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n"
"expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n" "expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n"
"active" = "💡 Hoạt động: ✅ Có\r\n" "active" = "💡 Đang hoạt động: {{ .Enable }}\r\n"
"inactive" = "💡 Hoạt động: ❌ Không\r\n" "enabled" = "🚨 Đã bật: {{ .Enable }}\r\n"
"online" = "🌐 Trạng thái kết nối: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n" "upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n"
"download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n" "download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n"
@@ -494,10 +559,13 @@
"TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n" "TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Sự cạn kiệt {{ .Type }}:\r\n" "exhaustedMsg" = "🚨 Sự cạn kiệt {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Số lần cạn kiệt {{ .Type }}:\r\n" "exhaustedCount" = "🚨 Số lần cạn kiệt {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Khách hàng trực tuyến: {{ .Count }}\r\n"
"disabled" = "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n" "disabled" = "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Thời gian sao lưu: {{ .Time }}\r\n" "backupTime" = "🗄 Thời gian sao lưu: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n"
"yes" = "✅ Có"
"no" = "❌ Không"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ Đóng Bàn Phím" "closeKeyboard" = "❌ Đóng Bàn Phím"
@@ -507,11 +575,13 @@
"confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?" "confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?"
"confirmClearIps" = "✅ Xác Nhận Xóa Các IP?" "confirmClearIps" = "✅ Xác Nhận Xóa Các IP?"
"confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?" "confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?"
"confirmToggle" = "✅ Xác nhận Bật/Tắt người dùng?"
"dbBackup" = "Tải bản sao lưu cơ sở dữ liệu" "dbBackup" = "Tải bản sao lưu cơ sở dữ liệu"
"serverUsage" = "Sử Dụng Máy Chủ" "serverUsage" = "Sử Dụng Máy Chủ"
"getInbounds" = "Lấy cổng vào" "getInbounds" = "Lấy cổng vào"
"depleteSoon" = "Depleted Soon" "depleteSoon" = "Depleted Soon"
"clientUsage" = "Lấy Sử Dụng" "clientUsage" = "Lấy Sử Dụng"
"onlines" = "Khách hàng trực tuyến"
"commands" = "Lệnh" "commands" = "Lệnh"
"refresh" = "🔄 Cập Nhật" "refresh" = "🔄 Cập Nhật"
"clearIPs" = "❌ Xóa IP" "clearIPs" = "❌ Xóa IP"
@@ -519,14 +589,16 @@
"selectTGUser" = "👤 Chọn Người Dùng Telegram" "selectTGUser" = "👤 Chọn Người Dùng Telegram"
"selectOneTGUser" = "👤 Chọn một người dùng telegram:" "selectOneTGUser" = "👤 Chọn một người dùng telegram:"
"resetTraffic" = "📈 Đặt Lại Lưu Lượng" "resetTraffic" = "📈 Đặt Lại Lưu Lượng"
"resetExpire" = "📅 Đặt Lại Ngày Hết Hạn" "resetExpire" = "📅 Thay đổi ngày hết hạn"
"ipLog" = "🔢 Nhật ký địa chỉ IP" "ipLog" = "🔢 Nhật ký địa chỉ IP"
"ipLimit" = "🔢 Giới Hạn địa chỉ IP" "ipLimit" = "🔢 Giới Hạn địa chỉ IP"
"setTGUser" = "👤 Đặt Người Dùng Telegram" "setTGUser" = "👤 Đặt Người Dùng Telegram"
"toggle" = "🔘 Bật / Tắt" "toggle" = "🔘 Bật / Tắt"
"custom" = "🔢 Tùy chỉnh" "custom" = "🔢 Tùy chỉnh"
"confirmNumber" = "✅ Xác nhận : {{ .Num }}" "confirmNumber" = "✅ Xác nhận: {{ .Num }}"
"confirmNumberAdd" = "✅ Xác nhận thêm: {{ .Num }}"
"limitTraffic" = "🚧 Giới hạn lưu lượng" "limitTraffic" = "🚧 Giới hạn lưu lượng"
"getBanLogs" = "Cấm nhật ký"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Thành công!" "successfulOperation" = "✅ Thành công!"
@@ -546,5 +618,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công." "removedTGUserSuccess" = "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công."
"enableSuccess" = "✅ {{ .Email }} : Đã Bật Thành Công." "enableSuccess" = "✅ {{ .Email }} : Đã Bật Thành Công."
"disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công." "disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công."
"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <b>{{ .TgUserID }}</b>" "askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <code>{{ .TgUserID }}</code>"
"askToAddUserName" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng tên người dùng hoặc ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nTên người dùng của bạn: <b>@{{ .TgUserName }}</b>\r\n\r\nID người dùng của bạn: <b>{{ .TgUserID }}</b>"

View File

@@ -10,8 +10,8 @@
"remark" = "备注" "remark" = "备注"
"enable" = "启用" "enable" = "启用"
"protocol" = "协议" "protocol" = "协议"
"search" = "搜" "search" = "搜"
"filter" = "过滤器" "filter" = "筛选"
"loading" = "加载中..." "loading" = "加载中..."
"second" = "秒" "second" = "秒"
"minute" = "分钟" "minute" = "分钟"
@@ -30,8 +30,8 @@
"sure" = "确定" "sure" = "确定"
"encryption" = "加密" "encryption" = "加密"
"transmission" = "传输" "transmission" = "传输"
"host" = "主持人" "host" = "Host"
"path" = "小路" "path" = "Path"
"camouflage" = "伪装" "camouflage" = "伪装"
"status" = "状态" "status" = "状态"
"enabled" = "开启" "enabled" = "开启"
@@ -49,9 +49,12 @@
"install" = "安装" "install" = "安装"
"clients" = "客户端" "clients" = "客户端"
"usage" = "用法" "usage" = "用法"
"secretToken" = "秘密令牌" "secretToken" = "安全密钥"
"remained" = "仍然存在" "remained" = "剩余"
"security" = "安全" "security" = "安全"
"secAlertTitle" = "安全警报"
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
"secAlertConf" = "某些配置已被确定为容易受到攻击,促使立即采取行动以加强安全协议并防范潜在的安全漏洞。"
[menu] [menu]
"dashboard" = "系统状态" "dashboard" = "系统状态"
@@ -62,7 +65,7 @@
"link" = "管理" "link" = "管理"
[pages.login] [pages.login]
"title" = "登录" "title" = "欢迎"
"loginAgain" = "登录时效已过,请重新登录" "loginAgain" = "登录时效已过,请重新登录"
[pages.login.toasts] [pages.login.toasts]
@@ -76,10 +79,10 @@
"title" = "系统状态" "title" = "系统状态"
"memory" = "内存" "memory" = "内存"
"hard" = "硬盘" "hard" = "硬盘"
"xrayStatus" = "状态" "xrayStatus" = "Xray"
"stopXray" = "停止" "stopXray" = "停止"
"restartXray" = "重启" "restartXray" = "重启"
"xraySwitch" = "切换版本" "xraySwitch" = "版本"
"xraySwitchClick" = "点击你想切换的版本" "xraySwitchClick" = "点击你想切换的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容" "xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
"operationHours" = "系统正常运行时间" "operationHours" = "系统正常运行时间"
@@ -134,7 +137,7 @@
"destinationPort" = "目标端口" "destinationPort" = "目标端口"
"targetAddress" = "目标地址" "targetAddress" = "目标地址"
"monitorDesc" = "默认留空即可" "monitorDesc" = "默认留空即可"
"meansNoLimit" = "表示不限制" "meansNoLimit" = " = 无限制单位GB)"
"totalFlow" = "总流量" "totalFlow" = "总流量"
"leaveBlankToNeverExpire" = "留空则永不到期" "leaveBlankToNeverExpire" = "留空则永不到期"
"noRecommendKeepDefault" = "没有特殊需求保持默认即可" "noRecommendKeepDefault" = "没有特殊需求保持默认即可"
@@ -148,7 +151,7 @@
"client" = "客户" "client" = "客户"
"export" = "导出链接" "export" = "导出链接"
"clone" = "克隆" "clone" = "克隆"
"cloneInbound" = "创造" "cloneInbound" = "克隆"
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆" "cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
"cloneInboundOk" = "从创建克隆" "cloneInboundOk" = "从创建克隆"
"resetAllTraffic" = "重置所有入站流量" "resetAllTraffic" = "重置所有入站流量"
@@ -173,12 +176,12 @@
"setDefaultCert" = "从面板设置证书" "setDefaultCert" = "从面板设置证书"
"xtlsDesc" = "Xray核心需要1.7.5" "xtlsDesc" = "Xray核心需要1.7.5"
"realityDesc" = "Xray核心需要1.8.0及以上版本" "realityDesc" = "Xray核心需要1.8.0及以上版本"
"telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "telegramDesc" = "使用聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称" "subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
"info" = "信息" "info" = "信息"
"same" = "相同" "same" = "相同"
"inboundData" = "入站数据" "inboundData" = "入站数据"
"copyToClipboard" = "复制到剪贴板" "exportInbound" = "出口 入境"
"import"="导入" "import"="导入"
"importInbound" = "导入入站" "importInbound" = "导入入站"
@@ -195,26 +198,27 @@
"prefix" = "前缀" "prefix" = "前缀"
"postfix" = "后缀" "postfix" = "后缀"
"delayedStart" = "首次使用后开始" "delayedStart" = "首次使用后开始"
"expireDays" = "过期天数" "expireDays" = "期间"
"days" = "天" "days" = "天"
"renew" = "自动续订" "renew" = "自动续订"
"renewDesc" = "期后自动续订。0 = 禁用" "renewDesc" = "期后自动续订。(0 = 禁用)(单元: 天)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "获取" "obtain" = "获取"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"requestHeader" = "请求头" "request" = "要求"
"name" = "名称" "response" = "回复"
"value" = "" "name" = "姓名"
"value" = "价值"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"requestVersion" = "请求版本" "version" = "版本"
"requestMethod" = "请求方法" "method" = "方法"
"requestPath" = "请求路径" "path" = "小路"
"responseVersion" = "响应版本" "status" = "地位"
"responseStatus" = "响应状态" "statusDescription" = "状态说明"
"responseStatusDescription" = "响应状态说明" "requestHeader" = "请求头"
"responseHeader" = "响应头" "responseHeader" = "响应头"
[pages.inbounds.stream.quic] [pages.inbounds.stream.quic]
@@ -246,6 +250,9 @@
"pageSize" = "分页大小" "pageSize" = "分页大小"
"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" "pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用"
"remarkModel" = "备注模型和分隔符" "remarkModel" = "备注模型和分隔符"
"datepicker" = "日期选择器"
"datepickerPlaceholder" = "选择日期"
"datepickerDescription" = "选择器日历类型指定到期日期"
"sampleRemark" = "备注示例" "sampleRemark" = "备注示例"
"oldUsername" = "原用户名" "oldUsername" = "原用户名"
"currentPassword" = "原密码" "currentPassword" = "原密码"
@@ -255,6 +262,8 @@
"telegramBotEnableDesc" = "重启面板生效" "telegramBotEnableDesc" = "重启面板生效"
"telegramToken" = "电报机器人TOKEN" "telegramToken" = "电报机器人TOKEN"
"telegramTokenDesc" = "重启面板生效" "telegramTokenDesc" = "重启面板生效"
"telegramProxy" = "Socks5 代理"
"telegramProxyDesc" = "如果您需要 Socks5 代理来连接 Telegram。 根据指南调整其设置。"
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效" "telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。" "telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。"
"telegramNotifyTime" = "电报机器人通知时间" "telegramNotifyTime" = "电报机器人通知时间"
@@ -296,6 +305,8 @@
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期" "subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用" "subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
"fragment" = "碎片"
"fragmentDesc" = "启用 TLS hello 数据包分段"
[pages.xray] [pages.xray]
"title" = "Xray 设置" "title" = "Xray 设置"
@@ -305,6 +316,8 @@
"advancedTemplate" = "高级模板部件" "advancedTemplate" = "高级模板部件"
"generalConfigs" = "通用配置" "generalConfigs" = "通用配置"
"generalConfigsDesc" = "这些选项将提供一般调整" "generalConfigsDesc" = "这些选项将提供一般调整"
"logConfigs"="日志"
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
"blockConfigs" = "阻塞配置" "blockConfigs" = "阻塞配置"
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" "blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
"blockCountryConfigs" = "阻止国家配置" "blockCountryConfigs" = "阻止国家配置"
@@ -327,8 +340,10 @@
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围" "PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
"Ads" = "屏蔽广告" "Ads" = "屏蔽广告"
"AdsDesc" = "修改配置模板屏蔽广告" "AdsDesc" = "修改配置模板屏蔽广告"
"Family" = "启用家庭友好配置" "Family" = "阻止恶意软件和成人内容"
"FamilyDesc" = "避免为家人连接到不安全的网站" "FamilyDesc" = "Cloudflare DNS 解析器可阻止恶意软件和成人内容以保护家庭."
"Security" = "阻止恶意软件、网络钓鱼和加密货币挖矿网站"
"SecurityDesc" = "更改安全防护配置模板."
"Speedtest" = "阻止测速网站" "Speedtest" = "阻止测速网站"
"SpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。" "SpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
"IRIp" = "禁止伊朗 IP 范围连接" "IRIp" = "禁止伊朗 IP 范围连接"
@@ -367,23 +382,36 @@
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由" "GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
"NetflixIPv4" = "为 Netflix 使用 IPv4" "NetflixIPv4" = "为 Netflix 使用 IPv4"
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由" "NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
"GoogleWARP" = "将谷歌路由到 WARP" "GoogleWARP" = "Google"
"GoogleWARPDesc" = "为谷歌添加路由到WARP" "GoogleWARPDesc" = "通过 WARP 将流量路由到 Google。"
"OpenAIWARP" = "OpenAI (ChatGPT) 路由到 WARP" "OpenAIWARP" = "OpenAI (ChatGPT)"
"OpenAIWARPDesc" = "OpenAIChatGPT路由添加到WARP" "OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)。"
"NetflixWARP" = "Netflix 路由到 WARP" "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "为Netflix添加路由到WARP" "NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix。"
"SpotifyWARP" = "将 Spotify 路由到 WARP" "MetaWARP"="Meta"
"SpotifyWARPDesc" = "为Spotify添加路由到WARP" "MetaWARPDesc" = "通过 WARP 将流量路由到 MetaInstagram、Facebook、WhatsApp、Threads..."
"AppleWARP" = "Apple"
"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple。"
"RedditWARP" = "Reddit"
"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit。"
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify。"
"IRWARP" = "将伊朗域名路由到 WARP" "IRWARP" = "将伊朗域名路由到 WARP"
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效" "IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
"Inbounds" = "界内" "Inbounds" = "入站"
"InboundsDesc" = "更改配置模板接受特殊客户端" "InboundsDesc" = "更改配置模板接受特殊客户端"
"Outbounds" = "出站" "Outbounds" = "出站"
"Balancers" = "平衡器"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式" "OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"Routings" = "路由规则" "Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要" "RoutingsDesc" = "每条规则的优先级都很重要"
"completeTemplate" = "全部" "completeTemplate" = "全部"
"logLevel" = "日志级别"
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
"accessLog" = "访问日志"
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
"errorLog" = "错误日志"
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
[pages.xray.rules] [pages.xray.rules]
"first" = "第一个" "first" = "第一个"
@@ -394,6 +422,7 @@
"dest" = "目的地" "dest" = "目的地"
"inbound" = "入站" "inbound" = "入站"
"outbound" = "出站" "outbound" = "出站"
"balancer" = "平衡器"
"info" = "信息" "info" = "信息"
"add" = "添加规则" "add" = "添加规则"
"edit" = "编辑规则" "edit" = "编辑规则"
@@ -405,7 +434,7 @@
"editOutbound" = "编辑出站" "editOutbound" = "编辑出站"
"editReverse" = "编辑反向" "editReverse" = "编辑反向"
"tag" = "标签" "tag" = "标签"
"tagDesc" = "独特的标签" "tagDesc" = "唯一标记"
"address" = "地址" "address" = "地址"
"reverse" = "反转" "reverse" = "反转"
"domain" = "域名" "domain" = "域名"
@@ -414,6 +443,38 @@
"portal" = "门户" "portal" = "门户"
"intercon" = "互连" "intercon" = "互连"
[pages.xray.balancer]
"addBalancer" = "添加平衡器"
"editBalancer" = "编辑平衡器"
"balancerStrategy" = "战略"
"balancerSelectors" = "选择器"
"tag" = "标签"
"tagDesc" = "唯一标记"
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用则只有outboundTag起作用。"
[pages.xray.wireguard]
"secretKey" = "密钥"
"publicKey" = "公钥"
"allowedIPs" = "允许的 IP"
"endpoint" = "终点"
"psk" = "共享密钥"
"domainStrategy" = "域策略"
[pages.xray.dns]
"enable" = "启用 DNS"
"enableDesc" = "启用内置 DNS 服务器"
"strategy" = "查询策略"
"strategyDesc" = "解析域名的总体策略"
"add" = "添加服务器"
"edit" = "编辑服务器"
"domains" = "域"
[pages.xray.fakedns]
"add" = "添加假 DNS"
"edit" = "编辑假 DNS"
"ipPool" = "IP 池子网"
"poolSize" = "池大小"
[pages.settings.security] [pages.settings.security]
"admin" = "管理员" "admin" = "管理员"
"secret" = "密钥" "secret" = "密钥"
@@ -437,6 +498,7 @@
"noIpRecord" = "❗ 没有IP记录" "noIpRecord" = "❗ 没有IP记录"
"noInbounds" = "❗ 没有找到入站连接!" "noInbounds" = "❗ 没有找到入站连接!"
"unlimited" = "♾ 无限制" "unlimited" = "♾ 无限制"
"add" = "添加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "天" "day" = "天"
@@ -445,6 +507,8 @@
"unknown" = "未知" "unknown" = "未知"
"inbounds" = "入站连接" "inbounds" = "入站连接"
"clients" = "客户端" "clients" = "客户端"
"offline" = "🔴 离线"
"online" = "🟢 在线的"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ 未知命令" "unknown" = "❗ 未知命令"
@@ -455,8 +519,8 @@
"status" = "✅ 机器人正常运行!" "status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!" "usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的ID为<code>{{ .ID }}</code>" "getID" = "🆔 您的ID为<code>{{ .ID }}</code>"
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n \r\n搜索入站连接包含客户端统计信息\r\n<code>/inbound [Remark]</code>" "helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n\r\n搜索入站连接包含客户端统计信息\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\n对于vmess/vless请使用UUID对于Trojan请使用密码。" "helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages] [tgbot.messages]
"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%" "cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%"
@@ -485,8 +549,9 @@
"port" = "🔌 端口:{{ .Port }}\r\n" "port" = "🔌 端口:{{ .Port }}\r\n"
"expire" = "📅 过期日期:{{ .Time }}\r\n" "expire" = "📅 过期日期:{{ .Time }}\r\n"
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n" "expireIn" = "📅 剩余时间:{{ .Time }}\r\n"
"active" = "💡 激活:\r\n" "active" = "💡 激活:{{ .Enable }}\r\n"
"inactive" = "💡 激活: ❌\r\n" "enabled" = "🚨 已启用:{{ .Enable }}\r\n"
"online" = "🌐 连接状态:{{ .Status }}\r\n"
"email" = "📧 邮箱:{{ .Email }}\r\n" "email" = "📧 邮箱:{{ .Email }}\r\n"
"upload" = "🔼 上传↑:{{ .Upload }}\r\n" "upload" = "🔼 上传↑:{{ .Upload }}\r\n"
"download" = "🔽 下载↓:{{ .Download }}\r\n" "download" = "🔽 下载↓:{{ .Download }}\r\n"
@@ -494,10 +559,13 @@
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n" "TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}\r\n" "exhaustedMsg" = "🚨 耗尽的{{ .Type }}\r\n"
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n" "exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n"
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" "disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n" "depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 备份时间:{{ .Time }}\r\n" "backupTime" = "🗄 备份时间:{{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n \r\n" "refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n"
"yes" = "✅ 是的"
"no" = "❌ 没有"
[tgbot.buttons] [tgbot.buttons]
"closeKeyboard" = "❌ 关闭键盘" "closeKeyboard" = "❌ 关闭键盘"
@@ -507,11 +575,13 @@
"confirmResetTraffic" = "✅ 确认重置流量?" "confirmResetTraffic" = "✅ 确认重置流量?"
"confirmClearIps" = "✅ 确认清除 IP" "confirmClearIps" = "✅ 确认清除 IP"
"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?" "confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?"
"confirmToggle" = "✅ 确认启用/禁用用户?"
"dbBackup" = "获取数据库备份" "dbBackup" = "获取数据库备份"
"serverUsage" = "服务器使用情况" "serverUsage" = "服务器使用情况"
"getInbounds" = "获取入站信息" "getInbounds" = "获取入站信息"
"depleteSoon" = "即将耗尽" "depleteSoon" = "即将耗尽"
"clientUsage" = "获取使用情况" "clientUsage" = "获取使用情况"
"onlines" = "在线客户"
"commands" = "命令" "commands" = "命令"
"refresh" = "🔄 刷新" "refresh" = "🔄 刷新"
"clearIPs" = "❌ 清除 IP" "clearIPs" = "❌ 清除 IP"
@@ -519,14 +589,16 @@
"selectTGUser" = "👤 选择 Telegram 用户" "selectTGUser" = "👤 选择 Telegram 用户"
"selectOneTGUser" = "👤 选择一个 Telegram 用户:" "selectOneTGUser" = "👤 选择一个 Telegram 用户:"
"resetTraffic" = "📈 重置流量" "resetTraffic" = "📈 重置流量"
"resetExpire" = "📅 重置过期天数" "resetExpire" = "📅 更改到期日期"
"ipLog" = "🔢 IP 日志" "ipLog" = "🔢 IP 日志"
"ipLimit" = "🔢 IP 限制" "ipLimit" = "🔢 IP 限制"
"setTGUser" = "👤 设置 Telegram 用户" "setTGUser" = "👤 设置 Telegram 用户"
"toggle" = "🔘 启用/禁用" "toggle" = "🔘 启用/禁用"
"custom" = "🔢 风俗" "custom" = "🔢 风俗"
"confirmNumber" = "✅ 确认 : {{ .Num }}" "confirmNumber" = "✅ 确认: {{ .Num }}"
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
"limitTraffic" = "🚧 交通限制" "limitTraffic" = "🚧 交通限制"
"getBanLogs" = "禁止日志"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ 成功的!" "successfulOperation" = "✅ 成功的!"
@@ -546,5 +618,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用户已成功移除。" "removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用户已成功移除。"
"enableSuccess" = "✅ {{ .Email }}:已成功启用。" "enableSuccess" = "✅ {{ .Email }}:已成功启用。"
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" "disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID<b>{{ .TgUserID }}</b>" "askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID<code>{{ .TgUserID }}</code>"
"askToAddUserName" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户名或用户ID。\r\n\r\n您的用户名<b>@{{ .TgUserName }}</b>\r\n\r\n您的用户ID<b>{{ .TgUserID }}</b>"

View File

@@ -240,11 +240,11 @@ func (s *Server) startTask() {
if err != nil { if err != nil {
logger.Warning("start xray failed:", err) logger.Warning("start xray failed:", err)
} }
// Check whether xray is running every 30 seconds // Check whether xray is running every second
s.cron.AddJob("@every 30s", job.NewCheckXrayRunningJob()) s.cron.AddJob("@every 1s", job.NewCheckXrayRunningJob())
// Check if xray needs to be restarted // Check if xray needs to be restarted every 30 seconds
s.cron.AddFunc("@every 10s", func() { s.cron.AddFunc("@every 30s", func() {
if s.xrayService.IsNeedRestartAndSetFalse() { if s.xrayService.IsNeedRestartAndSetFalse() {
err := s.xrayService.RestartXray(false) err := s.xrayService.RestartXray(false)
if err != nil { if err != nil {

570
x-ui.sh
View File

@@ -51,8 +51,16 @@ elif [[ "${release}" == "fedora" ]]; then
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1 echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "debian" ]]; then elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then if [[ ${os_version} -lt 11 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "almalinux" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use Almalinux 9 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "rocky" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use Rockylinux 9 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "arch" ]]; then elif [[ "${release}" == "arch" ]]; then
echo "Your OS is ArchLinux" echo "Your OS is ArchLinux"
@@ -62,13 +70,11 @@ elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian" echo "Your OS is Armbian"
fi fi
# Declare Variables # Declare Variables
log_folder="${XUI_LOG_FOLDER:=/var/log}" log_folder="${XUI_LOG_FOLDER:=/var/log}"
iplimit_log_path="${log_folder}/3xipl.log" iplimit_log_path="${log_folder}/3xipl.log"
iplimit_banned_log_path="${log_folder}/3xipl-banned.log" iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
confirm() { confirm() {
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
echo && read -p "$1 [Default $2]: " temp echo && read -p "$1 [Default $2]: " temp
@@ -111,7 +117,7 @@ install() {
} }
update() { update() {
confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "n" confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "y"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
LOGE "Cancelled" LOGE "Cancelled"
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@@ -126,6 +132,30 @@ update() {
fi fi
} }
custom_version() {
echo "Enter the panel version (like 2.0.0):"
read panel_version
if [ -z "$panel_version" ]; then
echo "Panel version cannot be empty. Exiting."
exit 1
fi
download_link="https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh"
# Use the entered panel version in the download link
install_command="bash <(curl -Ls $download_link) v$panel_version"
echo "Downloading and installing panel version $panel_version..."
eval $install_command
}
# Function to handle the deletion of the script file
delete_script() {
rm "$0" # Remove the script file itself
exit 1
}
uninstall() { uninstall() {
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n" confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
@@ -143,12 +173,13 @@ uninstall() {
rm /usr/local/x-ui/ -rf rm /usr/local/x-ui/ -rf
echo "" echo ""
echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it." echo -e "Uninstalled Successfully.\n"
echo "If you need to install this panel again, you can use below command:"
echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}"
echo "" echo ""
# Trap the SIGTERM signal
if [[ $# == 0 ]]; then trap delete_script SIGTERM
before_show_menu delete_script
fi
} }
reset_user() { reset_user() {
@@ -303,15 +334,56 @@ show_log() {
} }
show_banlog() { show_banlog() {
if test -f "${iplimit_banned_log_path}"; then if test -f "${iplimit_banned_log_path}"; then
if [[ -s "${iplimit_banned_log_path}" ]]; then if [[ -s "${iplimit_banned_log_path}" ]]; then
cat ${iplimit_banned_log_path} cat ${iplimit_banned_log_path}
else
echo -e "${red}Log file is empty.${plain}\n"
fi
else else
echo -e "${red}Log file is empty.${plain}\n" echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
fi
}
bbr_menu() {
echo -e "${green}\t1.${plain} Enable BBR"
echo -e "${green}\t2.${plain} Disable BBR"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
enable_bbr
;;
2)
disable_bbr
;;
*) echo "Invalid choice" ;;
esac
}
disable_bbr() {
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
echo -e "${yellow}BBR is not currently enabled.${plain}"
exit 0
fi
# Replace BBR with CUBIC configurations
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
# Apply changes
sysctl -p
# Verify that BBR is replaced with CUBIC
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
else
echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}"
fi fi
else
echo -e "${red}Log file not found. Please Install Fail2ban and IP Limit first.${plain}\n"
fi
} }
enable_bbr() { enable_bbr() {
@@ -322,19 +394,19 @@ enable_bbr() {
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu | debian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
;; ;;
centos) centos | almalinux | rocky)
yum -y update && yum -y install ca-certificates yum -y update && yum -y install ca-certificates
;; ;;
fedora) fedora)
dnf -y update && dnf -y install ca-certificates dnf -y update && dnf -y install ca-certificates
;; ;;
*) *)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 exit 1
;; ;;
esac esac
# Enable BBR # Enable BBR
@@ -459,6 +531,33 @@ show_xray_status() {
fi fi
} }
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}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
open_ports
;;
2)
sudo ufw status
;;
3)
delete_ports
;;
4)
sudo ufw disable
;;
*) echo "Invalid choice" ;;
esac
}
open_ports() { open_ports() {
if ! command -v ufw &>/dev/null; then if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..." echo "ufw firewall is not installed. Installing now..."
@@ -511,6 +610,37 @@ open_ports() {
ufw status | grep $ports ufw status | grep $ports
} }
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
# 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
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)
# Loop through the range and delete each port
for ((i = start_port; i <= end_port; i++)); do
ufw delete allow $i
done
else
ufw delete allow "$port"
fi
done
# Confirm that the ports are deleted
echo "Deleted the specified ports:"
ufw status | grep $ports
}
update_geo() { update_geo() {
local defaultBinFolder="/usr/local/x-ui/bin" local defaultBinFolder="/usr/local/x-ui/bin"
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
@@ -555,21 +685,24 @@ ssl_cert_issue_main() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
ssl_cert_issue ;; 1)
2) ssl_cert_issue
local domain="" ;;
read -p "Please enter your domain name to revoke the certificate: " domain 2)
~/.acme.sh/acme.sh --revoke -d ${domain} local domain=""
LOGI "Certificate revoked" read -p "Please enter your domain name to revoke the certificate: " domain
;; ~/.acme.sh/acme.sh --revoke -d ${domain}
3) LOGI "Certificate revoked"
local domain="" ;;
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain 3)
~/.acme.sh/acme.sh --renew -d ${domain} --force ;; local domain=""
*) echo "Invalid choice" ;; read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
~/.acme.sh/acme.sh --renew -d ${domain} --force
;;
*) echo "Invalid choice" ;;
esac esac
} }
@@ -585,15 +718,19 @@ ssl_cert_issue() {
fi fi
# install socat second # install socat second
case "${release}" in case "${release}" in
ubuntu|debian|armbian) ubuntu | debian | armbian)
apt update && apt install socat -y ;; apt update && apt install socat -y
centos) ;;
yum -y update && yum -y install socat ;; centos | almalinux | rocky)
fedora) yum -y update && yum -y install socat
dnf -y update && dnf -y install socat ;; ;;
*) fedora)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" dnf -y update && dnf -y install socat
exit 1 ;; ;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac esac
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "install socat failed, please check logs" LOGE "install socat failed, please check logs"
@@ -724,8 +861,8 @@ ssl_cert_issue_CF() {
LOGI "Certificate issued Successfully, Installing..." LOGI "Certificate issued Successfully, Installing..."
fi fi
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \ ~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \ --cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
--fullchain-file /root/cert/fullchain.cer --fullchain-file /root/cert/fullchain.cer
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
LOGE "Certificate installation failed, script exiting..." LOGE "Certificate installation failed, script exiting..."
exit 1 exit 1
@@ -756,41 +893,42 @@ warp_cloudflare() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) 1)
;; bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
2) ;;
warp a 2)
;; warp a
3) ;;
warp y 3)
;; warp y
4) ;;
warp u 4)
;; warp u
*) echo "Invalid choice" ;; ;;
*) echo "Invalid choice" ;;
esac esac
} }
run_speedtest() { run_speedtest() {
# Check if Speedtest is already installed # Check if Speedtest is already installed
if ! command -v speedtest &> /dev/null; then if ! command -v speedtest &>/dev/null; then
# If not installed, install it # If not installed, install it
local pkg_manager="" local pkg_manager=""
local speedtest_install_script="" local speedtest_install_script=""
if command -v dnf &> /dev/null; then if command -v dnf &>/dev/null; then
pkg_manager="dnf" pkg_manager="dnf"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v yum &> /dev/null; then elif command -v yum &>/dev/null; then
pkg_manager="yum" pkg_manager="yum"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v apt-get &> /dev/null; then elif command -v apt-get &>/dev/null; then
pkg_manager="apt-get" pkg_manager="apt-get"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
elif command -v apt &> /dev/null; then elif command -v apt &>/dev/null; then
pkg_manager="apt" pkg_manager="apt"
speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh" speedtest_install_script="https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
fi fi
@@ -809,8 +947,11 @@ run_speedtest() {
} }
create_iplimit_jails() { create_iplimit_jails() {
# Use default bantime if not passed => 5 minutes # Use default bantime if not passed => 30 minutes
local bantime="${1:-5}" local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl] [3x-ipl]
@@ -854,7 +995,7 @@ actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
[Init] [Init]
EOF EOF
echo -e "${green}Created Ip Limit jail files with a bantime of ${bantime} minutes.${plain}" echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
} }
iplimit_remove_conflicts() { iplimit_remove_conflicts() {
@@ -882,62 +1023,80 @@ iplimit_main() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
0) 0)
show_menu ;; show_menu
1) ;;
confirm "Proceed with installation of Fail2ban & IP Limit?" "y" 1)
if [[ $? == 0 ]]; then confirm "Proceed with installation of Fail2ban & IP Limit?" "y"
install_iplimit if [[ $? == 0 ]]; then
else install_iplimit
iplimit_main else
fi ;; iplimit_main
2) fi
read -rp "Please enter new Ban Duration in Minutes [default 5]: " NUM ;;
if [[ $NUM =~ ^[0-9]+$ ]]; then 2)
create_iplimit_jails ${NUM} read -rp "Please enter new Ban Duration in Minutes [default 30]: " NUM
systemctl restart fail2ban if [[ $NUM =~ ^[0-9]+$ ]]; then
else create_iplimit_jails ${NUM}
echo -e "${red}${NUM} is not a number! Please, try again.${plain}" systemctl restart fail2ban
fi else
iplimit_main ;; echo -e "${red}${NUM} is not a number! Please, try again.${plain}"
3) fi
confirm "Proceed with Unbanning everyone from IP Limit jail?" "y" iplimit_main
if [[ $? == 0 ]]; then ;;
fail2ban-client reload --restart --unban 3x-ipl 3)
echo -e "${green}All users Unbanned successfully.${plain}" confirm "Proceed with Unbanning everyone from IP Limit jail?" "y"
iplimit_main if [[ $? == 0 ]]; then
else fail2ban-client reload --restart --unban 3x-ipl
echo -e "${yellow}Cancelled.${plain}" truncate -s 0 "${iplimit_banned_log_path}"
fi echo -e "${green}All users Unbanned successfully.${plain}"
iplimit_main ;; iplimit_main
4) else
show_banlog echo -e "${yellow}Cancelled.${plain}"
;; fi
5) iplimit_main
service fail2ban status ;;
;; 4)
show_banlog
;;
5)
service fail2ban status
;;
6) 6)
remove_iplimit ;; remove_iplimit
*) echo "Invalid choice" ;; ;;
*) echo "Invalid choice" ;;
esac esac
} }
install_iplimit() { install_iplimit() {
if ! command -v fail2ban-client &>/dev/null; then if ! command -v fail2ban-client &>/dev/null; then
echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n" echo -e "${green}Fail2ban is not installed. Installing now...!${plain}\n"
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu|debian) ubuntu | debian)
apt update && apt install fail2ban -y ;; apt update && apt install fail2ban -y
centos) ;;
yum -y update && yum -y install fail2ban ;; centos | almalinux | rocky)
fedora) yum update -y && yum install epel-release -y
dnf -y update && dnf -y install fail2ban ;; yum -y install fail2ban
*) ;;
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n" fedora)
exit 1 ;; dnf -y update && dnf -y install fail2ban
;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac esac
if ! command -v fail2ban-client &>/dev/null; then
echo -e "${red}Fail2ban installation failed.${plain}\n"
exit 1
fi
echo -e "${green}Fail2ban installed successfully!${plain}\n" echo -e "${green}Fail2ban installed successfully!${plain}\n"
else else
echo -e "${yellow}Fail2ban is already installed.${plain}\n" echo -e "${yellow}Fail2ban is already installed.${plain}\n"
@@ -965,6 +1124,7 @@ install_iplimit() {
# Launching fail2ban # Launching fail2ban
if ! systemctl is-active --quiet fail2ban; then if ! systemctl is-active --quiet fail2ban; then
systemctl start fail2ban systemctl start fail2ban
systemctl enable fail2ban
else else
systemctl restart fail2ban systemctl restart fail2ban
fi fi
@@ -974,41 +1134,53 @@ install_iplimit() {
before_show_menu before_show_menu
} }
remove_iplimit(){ remove_iplimit() {
echo -e "${green}\t1.${plain} Only remove IP Limit configurations" echo -e "${green}\t1.${plain} Only remove IP Limit configurations"
echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit" echo -e "${green}\t2.${plain} Uninstall Fail2ban and IP Limit"
echo -e "${green}\t0.${plain} Abort" echo -e "${green}\t0.${plain} Abort"
read -p "Choose an option: " num read -p "Choose an option: " num
case "$num" in case "$num" in
1) 1)
rm -f /etc/fail2ban/filter.d/3x-ipl.conf rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf rm -f /etc/fail2ban/jail.d/3x-ipl.conf
systemctl restart fail2ban systemctl restart fail2ban
echo -e "${green}IP Limit removed successfully!${plain}\n" echo -e "${green}IP Limit removed successfully!${plain}\n"
before_show_menu ;; before_show_menu
2) ;;
rm -rf /etc/fail2ban 2)
systemctl stop fail2ban rm -rf /etc/fail2ban
case "${release}" in systemctl stop fail2ban
ubuntu|debian) case "${release}" in
apt-get purge fail2ban -y;; ubuntu | debian)
centos) apt-get remove -y fail2ban
yum remove fail2ban -y;; apt-get purge -y fail2ban -y
fedora) apt-get autoremove -y
dnf remove fail2ban -y;; ;;
*) centos | almalinux | rocky)
echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n" yum remove fail2ban -y
exit 1 ;; yum autoremove -y
esac ;;
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n" fedora)
before_show_menu ;; dnf remove fail2ban -y
0) dnf autoremove -y
echo -e "${yellow}Cancelled.${plain}\n" ;;
iplimit_main ;;
*) *)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Unsupported operating system. Please uninstall Fail2ban manually.${plain}\n"
remove_iplimit ;; exit 1
;;
esac
echo -e "${green}Fail2ban and IP Limit removed successfully!${plain}\n"
before_show_menu
;;
0)
echo -e "${yellow}Cancelled.${plain}\n"
iplimit_main
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
remove_iplimit
;;
esac esac
} }
@@ -1035,36 +1207,37 @@ show_menu() {
${green}3X-ui Panel Management Script${plain} ${green}3X-ui Panel Management Script${plain}
${green}0.${plain} Exit Script ${green}0.${plain} Exit Script
———————————————— ————————————————
${green}1.${plain} Install x-ui ${green}1.${plain} Install
${green}2.${plain} Update x-ui ${green}2.${plain} Update
${green}3.${plain} Uninstall x-ui ${green}3.${plain} Custom Version
${green}4.${plain} Uninstall
———————————————— ————————————————
${green}4.${plain} Reset Username & Password & Secret Token ${green}5.${plain} Reset Username & Password & Secret Token
${green}5.${plain} Reset Panel Settings ${green}6.${plain} Reset Settings
${green}6.${plain} Change Panel Port ${green}7.${plain} Change Port
${green}7.${plain} View Current Panel Settings ${green}8.${plain} View Current Settings
———————————————— ————————————————
${green}8.${plain} Start x-ui ${green}9.${plain} Start
${green}9.${plain} Stop x-ui ${green}10.${plain} Stop
${green}10.${plain} Restart x-ui ${green}11.${plain} Restart
${green}11.${plain} Check x-ui Status ${green}12.${plain} Check Status
${green}12.${plain} Check x-ui Logs ${green}13.${plain} Check Logs
———————————————— ————————————————
${green}13.${plain} Enable x-ui On System Startup ${green}14.${plain} Enable Autostart
${green}14.${plain} Disable x-ui On System Startup ${green}15.${plain} Disable Autostart
———————————————— ————————————————
${green}15.${plain} SSL Certificate Management ${green}16.${plain} SSL Certificate Management
${green}16.${plain} Cloudflare SSL Certificate ${green}17.${plain} Cloudflare SSL Certificate
${green}17.${plain} IP Limit Management ${green}18.${plain} IP Limit Management
${green}18.${plain} WARP Management ${green}19.${plain} WARP Management
${green}20.${plain} Firewall Management
———————————————— ————————————————
${green}19.${plain} Enable BBR ${green}21.${plain} Enable BBR
${green}20.${plain} Update Geo Files ${green}22.${plain} Update Geo Files
${green}21.${plain} Active Firewall and open ports ${green}23.${plain} Speedtest by Ookla
${green}22.${plain} Speedtest by Ookla
" "
show_status show_status
echo && read -p "Please enter your selection [0-22]: " num echo && read -p "Please enter your selection [0-23]: " num
case "${num}" in case "${num}" in
0) 0)
@@ -1077,67 +1250,70 @@ show_menu() {
check_install && update check_install && update
;; ;;
3) 3)
check_install && uninstall check_install && custom_version
;; ;;
4) 4)
check_install && reset_user check_install && uninstall
;; ;;
5) 5)
check_install && reset_config check_install && reset_user
;; ;;
6) 6)
check_install && set_port check_install && reset_config
;; ;;
7) 7)
check_install && check_config check_install && set_port
;; ;;
8) 8)
check_install && start check_install && check_config
;; ;;
9) 9)
check_install && stop check_install && start
;; ;;
10) 10)
check_install && restart check_install && stop
;; ;;
11) 11)
check_install && status check_install && restart
;; ;;
12) 12)
check_install && show_log check_install && status
;; ;;
13) 13)
check_install && enable check_install && show_log
;; ;;
14) 14)
check_install && disable check_install && enable
;; ;;
15) 15)
ssl_cert_issue_main check_install && disable
;; ;;
16) 16)
ssl_cert_issue_CF ssl_cert_issue_main
;; ;;
17) 17)
iplimit_main ssl_cert_issue_CF
;; ;;
18) 18)
warp_cloudflare iplimit_main
;; ;;
19) 19)
enable_bbr warp_cloudflare
;; ;;
20) 20)
update_geo firewall_menu
;; ;;
21) 21)
open_ports bbr_menu
;; ;;
22) 22)
update_geo
;;
23)
run_speedtest run_speedtest
;; ;;
*) *)
LOGE "Please enter the correct number [0-22]" LOGE "Please enter the correct number [0-23]"
;; ;;
esac esac
} }

View File

@@ -50,7 +50,9 @@ func (x *XrayAPI) Init(apiPort int) (err error) {
} }
func (x *XrayAPI) Close() { func (x *XrayAPI) Close() {
x.grpcClient.Close() if x.grpcClient != nil {
x.grpcClient.Close()
}
x.HandlerServiceClient = nil x.HandlerServiceClient = nil
x.StatsServiceClient = nil x.StatsServiceClient = nil
x.isConnected = false x.isConnected = false
@@ -211,6 +213,7 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
continue continue
} }
isInbound := matchs[1] == "inbound" isInbound := matchs[1] == "inbound"
isOutbound := matchs[1] == "outbound"
tag := matchs[2] tag := matchs[2]
isDown := matchs[3] == "downlink" isDown := matchs[3] == "downlink"
if tag == "api" { if tag == "api" {
@@ -219,8 +222,9 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
traffic, ok := tagTrafficMap[tag] traffic, ok := tagTrafficMap[tag]
if !ok { if !ok {
traffic = &Traffic{ traffic = &Traffic{
IsInbound: isInbound, IsInbound: isInbound,
Tag: tag, IsOutbound: isOutbound,
Tag: tag,
} }
tagTrafficMap[tag] = traffic tagTrafficMap[tag] = traffic
traffics = append(traffics, traffic) traffics = append(traffics, traffic)

View File

@@ -16,7 +16,8 @@ type Config struct {
API json_util.RawMessage `json:"api"` API json_util.RawMessage `json:"api"`
Stats json_util.RawMessage `json:"stats"` Stats json_util.RawMessage `json:"stats"`
Reverse json_util.RawMessage `json:"reverse"` Reverse json_util.RawMessage `json:"reverse"`
FakeDNS json_util.RawMessage `json:"fakeDns"` FakeDNS json_util.RawMessage `json:"fakedns"`
Observatory json_util.RawMessage `json:"observatory"`
} }
func (c *Config) Equals(other *Config) bool { func (c *Config) Equals(other *Config) bool {

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