Compare commits

...

79 Commits

Author SHA1 Message Date
MHSanaei
61e068ab40 v2.1.0 2024-01-16 01:37:50 +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
MHSanaei
eec4d71097 [xray] add user field
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-12-19 14:02:17 +03:30
quydang
a2bdf23940 Update support for new architecture ARM devices and added support for more OS. (#1389)
* Add files via upload

* Delete web/translation/translate.vi_VN.toml

* Add files via upload

* Update translate.vi_VN.toml

* Update release.yml

* Update install.sh

* Update install.sh

* Update install.sh

* Update install.sh

* Update x-ui.sh

* Update docker.yml

* Update DockerInit.sh

* Update install.sh

* Update install.sh

* Update install.sh

* Update .github/workflows/release.yml

* Update README.md

---------

Co-authored-by: Ho3ein <ho3ein.sanaei@gmail.com>
2023-12-19 13:59:26 +03:30
MHSanaei
d08aaa0068 fix reverse edit/delete
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-12-19 13:51:44 +03:30
dependabot[bot]
1e1f947060 Bump google.golang.org/grpc from 1.60.0 to 1.60.1 (#1413)
* Added geographical options for Vietnamese people! (#1408)

* Update xray.html

Add : Add xray config domain option for Vietnamese people

* Update translate.vi_VN.toml

Add : Add xray config domain option for Vietnamese people!

* Update release.yml

Add : add Vietnam country geoip & geosite geodatabase files

* Update x-ui.sh

add: Added update of Vietnam geoip & geosite geodatabase!

* Update xray.html

fix :fix code errors

* Update xray.html

fix: fix code errors

* Update web/html/xui/xray.html

* Update web/html/xui/xray.html

---------

Co-authored-by: Ho3ein <ho3ein.sanaei@gmail.com>

* Bump google.golang.org/grpc from 1.60.0 to 1.60.1

Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.60.0 to 1.60.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.60.0...v1.60.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>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: vuong2023 <124447749+vuong2023@users.noreply.github.com>
Co-authored-by: Ho3ein <ho3ein.sanaei@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 13:36:44 +03:30
vuong2023
62ae7aaffa Added geographical options for Vietnamese people! (#1408)
* Update xray.html

Add : Add xray config domain option for Vietnamese people

* Update translate.vi_VN.toml

Add : Add xray config domain option for Vietnamese people!

* Update release.yml

Add : add Vietnam country geoip & geosite geodatabase files

* Update x-ui.sh

add: Added update of Vietnam geoip & geosite geodatabase!

* Update xray.html

fix :fix code errors

* Update xray.html

fix: fix code errors

* Update web/html/xui/xray.html

* Update web/html/xui/xray.html

---------

Co-authored-by: Ho3ein <ho3ein.sanaei@gmail.com>
2023-12-19 13:32:34 +03:30
dependabot[bot]
c234bbe9e1 Bump docker/metadata-action from 5.3.0 to 5.4.0 (#1412)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5.3.0...v5.4.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>
2023-12-19 13:07:30 +03:30
dependabot[bot]
ed38102f17 Bump golang.org/x/crypto from 0.15.0 to 0.17.0 (#1409)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 13:07:14 +03:30
Tara Rostami
69651ecfb5 Minor UI fixes (#1405)
* Update translate.en_US.toml

* Update stream_kcp.html

* Update vless.html

* Update shadowsocks.html

* Update external_proxy.html

* Update stream_sockopt.html

* Update sniffing.html

* Update tls_settings.html

* Update dokodemo.html

* Update shadowsocks.html

* Update socks.html

* Update stream_ws.html

* Update tls_settings.html

* Update index.html

* Update translate.en_US.toml

* Update translate.fa_IR.toml

* Update translate.ru_RU.toml

* Update translate.vi_VN.toml

* Update translate.zh_Hans.toml

* Update outbound.html

* Update outbound.html

* Update trojan.html

* Update vless.html

* Update stream_kcp.html

* Update xray.html

* Update stream_grpc.html

* Update custom.css

* Update stream_quic.html

* Update custom.css

* Update custom.css

* Update custom.css

* Update inbounds.html
2023-12-19 13:06:36 +03:30
MHSanaei
78d3680ced alpn order change 2023-12-18 17:21:49 +03:30
Tara Rostami
495bfb9683 Minor improvements in UI (#1399) 2023-12-17 18:46:50 +03:30
guard43ru
9b60b0fd45 Change port check (#1268)
We can use same port on difference IP's.
2023-12-16 22:18:07 +03:30
Dmitry Nefedov
c6881e5149 Fixed Russian translation (#1372) 2023-12-16 21:29:41 +03:30
Tara Rostami
5971e3f856 Better Login Animation (#1384)
* Update login.html

* Update login.html

* Update login.html

* Update login.html
2023-12-16 21:29:08 +03:30
MMR
647b72e4fa Correct spelling mistakes (#1387)
UDP instead of TDP
2023-12-16 21:28:56 +03:30
Tara Rostami
b9b74139bf Better login page & css correction (#1368)
* Update custom.css

* Update login.html

* Update inbounds.html

* Update custom.css

* Update custom.css
2023-12-14 16:45:02 +03:30
Dmitry Nefedov
653e26dad6 Update translate.ru_RU.toml (#1367) 2023-12-14 16:31:50 +03:30
MMR
0135be8757 simplify iran Geoip rule (#1369) 2023-12-14 15:38:10 +03:30
MHSanaei
76598dfa1a fix Russian lang #1358 2023-12-14 10:16:57 +03:30
MHSanaei
6a2019629b v2.0.1 2023-12-14 10:15:44 +03:30
MHSanaei
8d18c8e98f [gui] redesign forms
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2023-12-13 19:33:11 +03:30
quydang
82e2241bdd Update Vietnamese Translation (#1348)
* Add files via upload

* Delete web/translation/translate.vi_VN.toml

* Add files via upload

* Update translate.vi_VN.toml
2023-12-13 15:25:18 +03:30
Tara Rostami
24a0b143ae login - animation removed (#1344) 2023-12-13 02:11:07 +03:30
MHSanaei
4b894760a1 bug fix - xtls 2023-12-12 21:41:10 +03:30
Tara Rostami
e300fbc7cb Update inbound_info_modal.html (#1342)
* Update login.html

* Update custom.css

* Update inbound_info_modal.html

* Update custom.css

* Update custom.css
2023-12-12 20:04:01 +03:30
dependabot[bot]
038bbfaee1 Bump google.golang.org/grpc from 1.59.0 to 1.60.0 (#1338)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.59.0 to 1.60.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.59.0...v1.60.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>
2023-12-12 14:19:41 +03:30
dependabot[bot]
f13bfabde7 Bump github.com/pelletier/go-toml/v2 from 2.1.0 to 2.1.1 (#1337)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.1.0...v2.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-12 14:19:31 +03:30
Tara Rostami
775042459d Update CSS & README (#1335)
* Update inbound_client_table.html

* Update custom.css

* Delete media/1.png

* Delete media/2.png

* Delete media/3.png

* Delete media/4.png

* Delete media/5.png

* pic v2.0.0

* Update README.md

* Delete media/0.png

* Delete media/1.png

* Delete media/2.png

* Delete media/3.png

* Delete media/4.png

* Delete media/5.png

* Delete media/6.png

* pic v2.0.0

* Update README.md

* Delete media/2.png

* pic v2.0.0
2023-12-12 14:19:16 +03:30
MHSanaei
af54b34f3a v2.0.0 2023-12-11 15:15:06 +01:00
84 changed files with 6461 additions and 4301 deletions

View File

@@ -1,4 +1,4 @@
name: Release X-ui dockerhub
name: Release 3X-UI dockerhub
on:
push:
tags:
@@ -27,7 +27,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5.3.0
uses: docker/metadata-action@v5.5.0
with:
images: ghcr.io/${{ github.repository }}
@@ -36,6 +36,6 @@ jobs:
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64, linux/arm64/v8
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,4 +1,4 @@
name: Release 3X-ui
name: Release 3X-UI
on:
push:
@@ -10,7 +10,11 @@ jobs:
build:
strategy:
matrix:
platform: [amd64, arm64]
platform:
- amd64
- arm64
- armv7
- armv6
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
@@ -20,12 +24,15 @@ jobs:
uses: actions/setup-go@v5.0.0
with:
go-version: '1.21'
- name: Install dependencies for arm64
if: matrix.platform == 'arm64'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt install gcc-aarch64-linux-gnu
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then
sudo apt install gcc-arm-linux-gnueabihf
fi
- name: Build x-ui
run: |
@@ -33,7 +40,12 @@ jobs:
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ] || [ "${{ matrix.platform }}" == "armv6" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
fi
go build -o xui-release -v main.go
@@ -46,20 +58,31 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.7/"
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
rm -f Xray-linux-64.zip
else
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "armv6" ]; then
wget ${Xray_URL}Xray-linux-arm32-v6.zip
unzip Xray-linux-arm32-v6.zip
rm -f Xray-linux-arm32-v6.zip
fi
rm -f geoip.dat geosite.dat geoip_IR.dat geosite_IR.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/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }}
cd ../..

View File

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

View File

@@ -5,6 +5,7 @@ FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
WORKDIR /app
ARG TARGETARCH
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN apk --no-cache --update add \
build-base \

278
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/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
@@ -8,58 +8,34 @@
[![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)
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian,Vietnamese,Spanish)**
**If you think this project is helpful to you, you may wish to give a** :star2:
> **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
**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)
```
# 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.0`:
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.0.2`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.0.0
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.0.2
```
# SSL
```
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
You also can use `x-ui` menu then select `SSL Certificate Management`
# Features
- System Status Monitoring
- 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
## Manual Install & Upgrade
<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:
@@ -88,11 +64,13 @@ systemctl restart x-ui
</details>
# Install with Docker
## Install with Docker
<details>
<summary>Click for Docker details</summary>
#### Usage
1. Install Docker:
```sh
@@ -127,32 +105,106 @@ systemctl restart x-ui
</details>
# Default settings
## Recommended OS
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rockylinux 9+
## Languages
- English
- Farsi
- Chinese
- Russian
- Vietnamese
- Spanish
## 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>
<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
### Information
Before you set ssl on settings
- http://ip:2053/panel
- http://domain:2053/panel
After you set ssl on settings
- https://yourdomain:2053/panel
- **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>
# Xray Configurations:
## SSL Certificate
<details>
<summary>Click for Xray Configurations details</summary>
<summary>Click for SSL Certificate</summary>
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
### 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
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
</details>
## Xray Configurations
<details>
<summary>Click for Xray configurations details</summary>
#### Usage
**1.** Copy & paste into the Advanced Xray Configuration:
- [traffic](./media/configs/traffic.json)
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
@@ -160,28 +212,32 @@ After you set ssl on settings
- [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)
***Tip:*** *You don't need to do this for a fresh install.*
</details>
# [WARP Configuration](https://gitlab.com/fscarmen/warp) (Optional)
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
<details>
<summary>Click for WARP Configuration details</summary>
<summary>Click for WARP configuration details</summary>
#### Usage
If you want to use routing to WARP follow steps as below:
1. If you already installed warp, you can uninstall using below command:
**1.** If you already installed warp, you can uninstall using below command:
```sh
warp u
```
2. Install WARP on **socks proxy mode**:
**2.** Install WARP on **SOCKS Proxy Mode**:
```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)
**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)
Config Features:
@@ -191,12 +247,14 @@ If you want to use routing to WARP follow steps as below:
</details>
# IP Limit
## IP Limit
<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`:
@@ -222,32 +280,34 @@ If you want to use routing to WARP follow steps as below:
</details>
# Telegram Bot
## Telegram Bot
<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)
Set the robot-related parameters in the panel background, including:
#### Usage
- Tg robot Token
- 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
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:
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
- Login notification
@@ -262,9 +322,8 @@ Reference syntax:
- Check depleted users
- Receive backup by request and in periodic reports
- Multi language bot
</details>
# Setting up Telegram bot
### Setting up Telegram bot
- Start [Botfather](https://t.me/BotFather) in your Telegram account:
![Botfather](./media/botfather.png)
@@ -284,12 +343,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.
![User ID](./media/user-id.png)
</details>
# API routes
## API Routes
<details>
<summary>Click for API routes details</summary>
#### Usage
- `/login` with `POST` user data: `{username: '', password: ''}` for login
- `/panel/api/inbounds` base for following actions:
@@ -324,10 +386,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)
</details>
# Environment Variables
## Environment Variables
<details>
<summary>Click for Environment Variables details</summary>
<summary>Click for environment variables details</summary>
#### Usage
| Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ |
@@ -345,25 +409,21 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
</details>
# A Special Thanks To
- [alireza0](https://github.com/alireza0/)
## Supported Architectures and Devices
# Acknowledgment
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
- [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._
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
# Suggestion System
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
- Ubuntu 20.04+
- Debian 10+
- CentOS 8+
- Fedora 36+
- Arch Linux
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
# Pictures
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
## Preview
![1](./media/1.png)
![2](./media/2.png)
@@ -371,7 +431,17 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
![4](./media/4.png)
![5](./media/5.png)
![6](./media/6.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)

View File

@@ -1 +1 @@
2.0.0
2.1.0

61
go.mod
View File

@@ -10,13 +10,14 @@ require (
github.com/mymmrac/telego v0.28.0
github.com/nicksnyder/go-i18n/v2 v2.3.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.1.0
github.com/pelletier/go-toml/v2 v2.1.1
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.11
github.com/xtls/xray-core v1.8.6
github.com/shirou/gopsutil/v3 v3.23.12
github.com/valyala/fasthttp v1.51.0
github.com/xtls/xray-core v1.8.7
go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0
google.golang.org/grpc v1.59.0
google.golang.org/grpc v1.60.1
gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5
)
@@ -26,46 +27,45 @@ require (
github.com/bytedance/sonic v1.10.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // 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/fasthttp/router v1.4.22 // indirect
github.com/francoispqt/gojay v1.2.13 // 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/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // 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-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/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/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.0 // indirect
github.com/refraction-networking/utls v1.5.4 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect
github.com/refraction-networking/utls v1.6.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.2.17 // indirect
github.com/sagernet/sing-shadowsocks v0.2.5 // indirect
github.com/sagernet/sing v0.3.0 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
@@ -75,25 +75,24 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/mock v0.3.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.15.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/protobuf v1.31.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
lukechampine.com/blake3 v1.2.1 // indirect

123
go.sum
View File

@@ -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/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
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.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
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/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=
@@ -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/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/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.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=
@@ -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/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.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
github.com/go-playground/validator/v10 v10.16.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/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -109,19 +107,21 @@ 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-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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
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 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
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.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -136,8 +136,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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/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.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -156,15 +156,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/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-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
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/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
@@ -181,8 +181,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
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.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
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/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@@ -191,8 +191,8 @@ github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -208,10 +208,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/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/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ=
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/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -221,17 +221,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/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
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.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing v0.3.0 h1:PIDVFZHnQAAYRL1UYqNM+0k5s8f/tb1lUW6UDcQiOc8=
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/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/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -299,18 +299,18 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
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/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
github.com/xtls/xray-core v1.8.7 h1:lb8O1l3/eAg3YAXA6tLm5M6N7BsX8wxW9sJLjU3dHkA=
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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
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/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -319,11 +319,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-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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
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-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -338,8 +338,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-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -349,8 +349,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-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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -369,8 +369,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.8.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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -380,20 +381,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/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.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-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-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.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
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.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
@@ -406,19 +407,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-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@@ -26,7 +26,9 @@ echo "The OS release is: $release"
arch3xui() {
case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;;
armv8 | arm64 | aarch64) echo 'arm64' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;;
armv6* | armv6 | arm) echo 'armv6' ;;
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
esac
}
@@ -41,20 +43,34 @@ if [[ "${release}" == "centos" ]]; then
fi
elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
fi
elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
fi
elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
if [[ ${os_version} -lt 11 ]]; then
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
elif [[ "${release}" == "arch" ]]; then
echo "OS is ArchLinux"
echo "Your OS is ArchLinux"
elif [[ "${release}" == "manjaro" ]]; then
echo "Your OS is Manjaro"
elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian"
else
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
@@ -62,14 +78,14 @@ fi
install_base() {
case "${release}" in
centos|fedora)
yum install -y -q wget curl tar
centos|fedora|almalinux|rocky)
yum -y update && yum install -y -q wget curl tar
;;
arch)
pacman -Syu --noconfirm wget curl tar
arch|manjaro)
pacman -Syu && pacman -Syu --noconfirm wget curl tar
;;
*)
apt install -y -q wget curl tar
apt-get update && apt install -y -q wget curl tar
;;
esac
}
@@ -78,7 +94,7 @@ install_base() {
# This function will be called when user installed x-ui out of security
config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
read -p "Do you want to continue with the modification [y/n]?": config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username:" config_account
echo -e "${yellow}Your username will be:${config_account}${plain}"
@@ -128,7 +144,7 @@ install_x-ui() {
else
last_version=$1
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}
if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 88 KiB

BIN
media/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

View File

@@ -20,6 +20,7 @@ type SubService struct {
address string
showInfo bool
remarkModel string
datepicker string
inboundService service.InboundService
settingService service.SettingService
}
@@ -39,6 +40,10 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
if err != nil {
s.remarkModel = "-ieo"
}
s.datepicker, err = s.settingService.GetDatepicker()
if err != nil {
s.datepicker = "gregorian"
}
for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound)
if err != nil {
@@ -876,7 +881,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
orders['i'] = inbound.Remark
}
if len(extra) > 0 {
orders['e'] = extra
orders['o'] = extra
}
var remark []string

View File

@@ -18,6 +18,7 @@ body {
html {
--antd-wave-shadow-color: #008771;
line-height: 1.15;
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
@@ -55,7 +56,7 @@ style attribute {
}
.ant-table-tbody > tr > td,
.ant-table-thead > tr > th {
padding: 12px 16px;
padding: 12px 8px;
overflow-wrap: break-word;
}
.ant-table-thead > tr > th {
@@ -93,11 +94,6 @@ style attribute {
.ant-table-body {
overflow-x: auto !important;
}
tr.ant-table-expanded-row td>.ant-table-wrapper {
margin: -12px -16px -13px;
}
.ant-card-hoverable {
cursor: auto;
cursor: pointer;
@@ -137,6 +133,13 @@ tr.ant-table-expanded-row td>.ant-table-wrapper {
margin: 0.5rem;
padding: 0.5rem;
}
.ant-modal-body {
padding: 10px;
}
.ant-form-item-label {
line-height: 1.5;
padding: 8px 0 0;
}
}
.ant-layout-content {
@@ -414,6 +417,10 @@ tr.ant-table-expanded-row td>.ant-table-wrapper {
background-color: white;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-setting-textarea {
margin-top: 1.5rem;
}
@@ -674,7 +681,7 @@ tr.ant-table-expanded-row td>.ant-table-wrapper {
.dark .ant-table-row-expand-icon {
color: #fff;
background-color: #fff0;
border-color: #9ea2a8;
border-color: rgb(255 255 255 / 20%);
}
.dark .ant-table-row-expand-icon:hover {
@@ -794,16 +801,16 @@ tr.ant-table-expanded-row td>.ant-table-wrapper {
.has-warning .ant-input,
.has-warning .ant-input:hover {
background-color: #fff6e6;
border-color: #ffd98c;
background-color: #ffeee1;
border-color: #fec093;
}
.has-warning .ant-input::placeholder {
color: #faad14;
color: #f37b24;
}
.has-warning .ant-input:not([disabled]):hover {
border-color: #ffd98c;
border-color: #fec093;
}
.dark .has-warning .ant-input,
@@ -844,6 +851,17 @@ tr.ant-table-expanded-row td>.ant-table-wrapper {
.ant-menu,
.ant-radio-button-wrapper {
user-select: none;
-webkit-user-select: none;
}
.ant-calendar-date,
.ant-calendar-year-panel-year,
.ant-calendar-decade-panel-decade,
.ant-calendar-month-panel-month,
.ant-checkbox-inner,
.ant-checkbox-checked:after,
.ant-table-row-expand-icon {
border-radius: 6px;
}
.ant-calendar-date:hover {
@@ -902,7 +920,7 @@ li.ant-select-dropdown-menu-item:empty:after {
.ant-select-dropdown.ant-select-dropdown--multiple
.ant-select-dropdown-menu-item-selected:hover
.ant-select-selected-icon {
color: #3c89e8;
color: #008771;
}
.ant-select-selection:hover,
.ant-input-number-focused,
@@ -1044,6 +1062,11 @@ li.ant-select-dropdown-menu-item:empty:after {
overflow: clip;
}
.ant-modal-body,
.ant-collapse-content>.ant-collapse-content-box {
overflow-x: auto;
}
.ant-calendar-year-panel-year:hover,
.ant-calendar-decade-panel-decade:hover,
.ant-calendar-month-panel-month:hover,
@@ -1076,3 +1099,25 @@ li.ant-select-dropdown-menu-item:empty:after {
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
background-color: #313f5a;
}
.ant-select-dropdown,
.ant-popover-inner {
overflow-x: hidden;
}
.ant-popover-inner-content {
max-height: 400px;
overflow-y: auto;
}
.qr-bg {
background-color: #fff;
display: flex;
justify-content: center;
align-content: center;
padding: 0.5rem;
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;
}

View File

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

View File

@@ -8,6 +8,7 @@ const Protocols = {
Shadowsocks: "shadowsocks",
Socks: "socks",
HTTP: "http",
Wireguard: "wireguard"
};
const SSMethods = {
@@ -46,18 +47,27 @@ const ALPN_OPTION = {
HTTP1: "http/1.1",
};
const outboundDomainStrategies = [
const OutboundDomainStrategies = [
"AsIs",
"UseIP",
"UseIPv4",
"UseIPv6"
]
];
const WireguardDomainStrategy = [
"ForceIP",
"ForceIPv4",
"ForceIPv4v6",
"ForceIPv6",
"ForceIPv6v4"
];
Object.freeze(Protocols);
Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(ALPN_OPTION);
Object.freeze(outboundDomainStrategies);
Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy);
class CommonClass {
@@ -476,7 +486,7 @@ class Outbound extends CommonClass {
if(data.length !=2) return null;
switch(data[0].toLowerCase()){
case Protocols.VMess:
return this.fromVmessLink(JSON.parse(atob(data[1])));
return this.fromVmessLink(JSON.parse(Base64.decode(data[1])));
case Protocols.VLESS:
case Protocols.Trojan:
case 'ss':
@@ -493,8 +503,8 @@ class Outbound extends CommonClass {
if (network === 'tcp') {
stream.tcp = new TcpStreamSettings(
json.type,
json.host ? json.host.split(','): [],
json.path ? json.path.split(','): []);
json.host ?? '',
json.path ?? '');
} else if (network === 'kcp') {
stream.kcp = new KcpStreamSettings();
stream.type = json.type;
@@ -505,7 +515,7 @@ class Outbound extends CommonClass {
stream.network = 'http'
stream.http = new HttpStreamSettings(
json.path,
json.host ? json.host.split(',') : []);
json.host);
} else if (network === 'quic') {
stream.quic = new QuicStreamSettings(
json.host ? json.host : 'none',
@@ -570,7 +580,7 @@ class Outbound extends CommonClass {
let sni=url.searchParams.get('sni') ?? '';
let sid=url.searchParams.get('sid') ?? '';
let spx=url.searchParams.get('spx') ?? '';
stream.tls = new RealityStreamSettings(pbk, fp, sni, sid, spx);
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
}
let data = link.split('?');
@@ -625,6 +635,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
case Protocols.Socks: return new Outbound.SocksSettings();
case Protocols.HTTP: return new Outbound.HttpSettings();
case Protocols.Wireguard: return new Outbound.WireguardSettings();
default: return null;
}
}
@@ -640,6 +651,7 @@ Outbound.Settings = class extends CommonClass {
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
default: return null;
}
}
@@ -838,13 +850,14 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
};
}
};
Outbound.SocksSettings = class extends CommonClass {
constructor(address, port, user, password) {
constructor(address, port, user, pass) {
super();
this.address = address;
this.port = port;
this.user = user;
this.password = password;
this.pass = pass;
}
static fromJson(json={}) {
@@ -854,7 +867,7 @@ Outbound.SocksSettings = class extends CommonClass {
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
);
}
@@ -863,18 +876,18 @@ Outbound.SocksSettings = class extends CommonClass {
servers: [{
address: this.address,
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 {
constructor(address, port, user, password) {
constructor(address, port, user, pass) {
super();
this.address = address;
this.port = port;
this.user = user;
this.password = password;
this.pass = pass;
}
static fromJson(json={}) {
@@ -884,7 +897,7 @@ Outbound.HttpSettings = class extends CommonClass {
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
);
}
@@ -893,8 +906,91 @@ Outbound.HttpSettings = class extends CommonClass {
servers: [{
address: this.address,
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=Wireguard.generateKeypair().privateKey,
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(",") : 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.trafficDiff = "";
this.remarkModel = "-ieo";
this.datepicker = "gregorian";
this.tgBotEnable = false;
this.tgBotToken = "";
this.tgBotProxy = "";
this.tgBotChatId = "";
this.tgRunTime = "@daily";
this.tgBotBackup = false;

View File

@@ -6,6 +6,7 @@ const Protocols = {
DOKODEMO: 'dokodemo-door',
SOCKS: 'socks',
HTTP: 'http',
WIREGUARD: 'wireguard',
};
const SSMethods = {
@@ -235,6 +236,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
toJson() {
return {
version: this.version,
method: this.method,
path: ObjectUtil.clone(this.path),
headers: XrayCommonClass.toV2Headers(this.headers),
@@ -474,10 +476,10 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '',
rejectUnknownSni = false,
certificates=[new TlsStreamSettings.Cert()],
alpn=[ALPN_OPTION.HTTP1,ALPN_OPTION.H2],
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=new TlsStreamSettings.Settings()) {
super();
this.server = serverName;
this.sni = serverName;
this.minVersion = minVersion;
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
@@ -519,7 +521,7 @@ class TlsStreamSettings extends XrayCommonClass {
toJson() {
return {
serverName: this.server,
serverName: this.sni,
minVersion: this.minVersion,
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
@@ -602,7 +604,7 @@ class XtlsStreamSettings extends XrayCommonClass {
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
settings=new XtlsStreamSettings.Settings()) {
super();
this.server = serverName;
this.sni = serverName;
this.certs = certificates;
this.alpn = alpn;
this.settings = settings;
@@ -636,7 +638,7 @@ class XtlsStreamSettings extends XrayCommonClass {
toJson() {
return {
serverName: this.server,
serverName: this.sni,
certificates: XtlsStreamSettings.toJsonArray(this.certs),
alpn: this.alpn,
settings: this.settings,
@@ -764,16 +766,18 @@ class RealityStreamSettings 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();
this.publicKey = publicKey;
this.fingerprint = fingerprint;
this.serverName = serverName;
this.spiderX = spiderX;
}
static fromJson(json = {}) {
return new RealityStreamSettings.Settings(
json.publicKey,
json.fingerprint,
json.serverName,
json.spiderX,
);
}
@@ -781,6 +785,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
return {
publicKey: this.publicKey,
fingerprint: this.fingerprint,
serverName: this.serverName,
spiderX: this.spiderX,
};
}
@@ -794,6 +799,7 @@ class SockoptStreamSettings extends XrayCommonClass {
this.mark = mark;
this.tproxy = tproxy;
}
static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined;
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() {
return this.stream.security === 'xtls';
}
@@ -1018,19 +1012,7 @@ class Inbound extends XrayCommonClass {
this.stream.security = 'none';
}
}
get reality() {
return this.stream.security === 'reality';
}
set reality(isReality) {
if (isReality) {
this.stream.security = 'reality';
} else {
this.stream.security = 'none';
}
}
get network() {
return this.stream.network;
}
@@ -1080,8 +1062,8 @@ class Inbound extends XrayCommonClass {
}
get serverName() {
if (this.stream.isTls) return this.stream.tls.server;
if (this.stream.isXtls) return this.stream.xtls.server;
if (this.stream.isTls) return this.stream.tls.sni;
if (this.stream.isXtls) return this.stream.xtls.sni;
if (this.stream.isReality) return this.stream.reality.serverNames;
return "";
}
@@ -1142,11 +1124,6 @@ class Inbound extends XrayCommonClass {
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
canEnableTlsFlow() {
if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
@@ -1155,6 +1132,11 @@ class Inbound extends XrayCommonClass {
return false;
}
canEnableReality() {
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return ["tcp", "http", "grpc"].includes(this.network);
}
canEnableXtls() {
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
return this.network === "tcp";
@@ -1233,7 +1215,7 @@ class Inbound extends XrayCommonClass {
if (security === 'tls') {
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
obj.sni = this.stream.tls.server;
obj.sni = this.stream.tls.sni;
}
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
obj.fp = this.stream.tls.settings.fingerprint;
@@ -1311,8 +1293,8 @@ class Inbound extends XrayCommonClass {
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)){
params.set("sni", this.stream.tls.server);
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
params.set("flow", flow);
@@ -1326,8 +1308,8 @@ class Inbound extends XrayCommonClass {
if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.xtls.server)){
params.set("sni", this.stream.xtls.server);
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
params.set("sni", this.stream.xtls.sni);
}
params.set("flow", flow);
}
@@ -1425,8 +1407,8 @@ class Inbound extends XrayCommonClass {
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)){
params.set("sni", this.stream.tls.server);
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
}
}
@@ -1506,8 +1488,8 @@ class Inbound extends XrayCommonClass {
if(this.stream.tls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (!ObjectUtil.isEmpty(this.stream.tls.server)){
params.set("sni", this.stream.tls.server);
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
params.set("sni", this.stream.tls.sni);
}
}
}
@@ -1533,8 +1515,8 @@ class Inbound extends XrayCommonClass {
if(this.stream.xtls.settings.allowInsecure){
params.set("allowInsecure", "1");
}
if (this.stream.xtls.settings.serverName !== ''){
params.set("sni", this.stream.xtls.settings.serverName);
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
params.set("sni", this.stream.xtls.sni);
}
params.set("flow", flow);
}
@@ -1601,13 +1583,13 @@ class Inbound extends XrayCommonClass {
if(this.clients){
let links = [];
this.clients.forEach((client) => {
genAllLinks(remark,remarkModel,client).forEach(l => {
this.genAllLinks(remark,remarkModel,client).forEach(l => {
links.push(l.link);
})
});
return links.join('\r\n');
} 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(this.listen, this.port, 'same', remark);
return '';
}
}
@@ -1657,7 +1639,8 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(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;
}
}
@@ -1671,6 +1654,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
default: return null;
}
}
@@ -1797,8 +1781,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
Protocols.VLESS,
json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
json.decryption || 'none',
json.fallbacks.map(fallback => Inbound.VLESSSettings.Fallback.fromJson(fallback)),
);
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),);
}
toJson() {
@@ -1925,8 +1908,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
return new Inbound.TrojanSettings(
Protocols.TROJAN,
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
json.fallbacks.map(fallback => Inbound.TrojanSettings.Fallback.fromJson(fallback))
);
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
}
toJson() {
@@ -2275,3 +2257,69 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
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());
}
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(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
}
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
json.publicKey,
json.preSharedKey,
json.allowedIPs,
json.keepAlive
);
}
toJson() {
return {
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;
}
}
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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g.POST("/update", a.updateSetting)
g.GET("/getXrayResult", a.getXrayResult)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
}
func (a *XraySettingController) getXraySetting(c *gin.Context) {
@@ -61,3 +62,25 @@ func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
func (a *XraySettingController) getXrayResult(c *gin.Context) {
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")
println(license)
resp, err = a.XraySettingService.SetWarpLicence(license)
}
jsonObj(c, resp, err)
}

View File

@@ -28,6 +28,7 @@ type AllSetting struct {
RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
@@ -47,6 +48,7 @@ type AllSetting struct {
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubURI string `json:"subURI" form:"subURI"`
Datepicker string `json:"datepicker" form:"datepicker"`
}
func (s *AllSetting) CheckValid() error {
@@ -72,8 +74,8 @@ func (s *AllSetting) CheckValid() error {
return common.NewError("Sub port is not a valid port:", s.SubPort)
}
if s.SubPort == s.WebPort {
return common.NewError("Sub and Web could not use same port:", s.SubPort)
if (s.SubPort == s.WebPort) && (s.WebListen == s.SubListen) {
return common.NewError("Sub and Web could not use same ip:port, ", s.SubListen, ":", s.SubPort, " & ", s.WebListen, ":", s.WebPort)
}
if s.WebCertFile != "" || s.WebKeyFile != "" {

View File

@@ -9,12 +9,12 @@
</a-tag>
<template v-if="app.subSettings.enable && qrModal.subId">
<a-divider>Subscription</a-divider>
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
</template>
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
<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>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
</template>
</a-modal>
@@ -72,7 +72,7 @@
});
},
genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+subID;
return app.subSettings.subURI+subID;
}
},
updated() {
@@ -87,4 +87,4 @@
});
</script>
{{end}}
{{end}}

View File

@@ -27,6 +27,7 @@
text-align: center;
align-items: center;
justify-content: center;
width: 100%;
}
.title {
font-size: 32px;
@@ -55,44 +56,12 @@
opacity: 1;
}
}
@keyframes wave {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.wave {
opacity: 0.6;
position: absolute;
bottom: 40%;
left: 50%;
width: 6000px;
height: 6000px;
background-color: rgba(0, 135, 113, 0.08);
margin-left: -3000px;
transform-origin: 50% 48%;
border-radius: 46%;
animation: wave 72s infinite linear;
pointer-events: none;
}
.wave2 {
animation: wave 88s infinite linear;
opacity: 0.3;
}
.wave3 {
animation: wave 80s infinite linear;
opacity: 0.1;
}
.under {
background-color: #dbf5ed;
}
.dark .wave {
background: rgb(10 117 87 / 20%);
background-color: #c7ebe2;
z-index: 0;
}
.dark .under {
background-color: #101828;
background-color: #0f2d32;
}
.dark #login {
background-color: #151f31;
@@ -100,12 +69,10 @@
.dark h1 {
color: rgba(255, 255, 255, 0.85);
}
.ant-form-item {
margin-bottom: 16px;
}
.ant-btn-primary-login {
color: #008771;
background-color: #eef9f7;
border-color: #89d9cc;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
box-shadow: none;
width: 100%;
}
.ant-btn-primary-login:focus,
@@ -147,6 +114,7 @@
position: relative;
border-radius: 25px;
width: 100%;
transition: all 0.3s cubic-bezier(.645,.045,.355,1);
}
.dark .wave-btn-bg {
color: #fff;
@@ -156,10 +124,10 @@
background-origin: border-box;
background-clip: padding-box, border-box;
background-size: 300%;
animation: wave-btn-tara 4s ease infinite;
transition: all 0.5s ease;
width: 100%;
z-index: 1;
}
.dark .wave-btn-bg:hover {animation: wave-btn-tara 4s ease infinite;}
.dark .wave-btn-bg-cl {
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
radial-gradient(circle at left top, #006655, #009980, #006655) !important;
@@ -215,14 +183,84 @@
background-position-x: 0;
box-shadow: none;
}
.waves-header {
position: fixed;
width: 100%;
text-align: center;
background-color: #dbf5ed;
color: white;
z-index: -1;
}
.dark .waves-header {
background-color: #101828;
}
.waves-inner-header {
height: 50vh;
width: 100%;
margin: 0;
padding: 0;
}
.waves {
position: relative;
width: 100%;
height: 15vh;
margin-bottom: -5px; /*Fix for safari gap*/
min-height: 100px;
max-height: 150px;
}
.parallax > use {
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
}
.dark .parallax > use {
fill: rgb(10 117 87 / 20%);
}
.parallax > use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
opacity: 0.2;
}
.parallax > use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
opacity: 0.4;
}
.parallax > use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
opacity: 0.6;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px, 0, 0);
}
100% {
transform: translate3d(85px, 0, 0);
}
}
@media (max-width: 768px) {
.waves {
height: 40px;
min-height: 40px;
}
}
</style>
<body>
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
<transition name="list" appear>
<a-layout-content class="under" style="min-height: 0;">
<div class='wave'></div>
<div class='wave wave2'></div>
<div class='wave wave3'></div>
<div class="waves-header">
<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">
<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" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="rgba(0, 135, 113, 0.08)" />
</g>
</svg>
</div>
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
<a-row type="flex" justify="center">
@@ -252,9 +290,8 @@
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<div class="wave-btn-bg wave-btn-bg-cl">
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
:style="loading ? { width: '50px' } : { display: 'inline-block' }">
<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">
[[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>
</div>

View File

@@ -2,206 +2,125 @@
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title"
@ok="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.client.method" }}</td>
<td>
<a-form-item>
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="0">Random</a-select-option>
<a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>1">
<td>{{ i18n "pages.client.first" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>1">
<td>{{ i18n "pages.client.last" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.lastNum"
:min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>0">
<td>{{ i18n "pages.client.prefix" }}</td>
<td>
<a-form-item>
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod>2">
<td>{{ i18n "pages.client.postfix" }}</td>
<td>
<a-form-item>
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.emailMethod < 2">
<td>{{ i18n "pages.client.clientCount" }}</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="clientsBulkModal.flow" style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="app.subSettings.enable">
<td>Subscription
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="app.tgBotEnable">
<td>Telegram ID
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<label>
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</label>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td v-if="clientsBulkModal.inbound.xtls">
<label>Flow</label>
</td>
<td v-if="clientsBulkModal.inbound.xtls">
<a-form-item>
<a-select v-model="clientsBulkModal.flow" style="width: 200px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientsBulkModal.delayedStart"
@click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"
style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.expiryTime != 0">
<td>
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.client.method" }}'>
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="0">Random</a-select-option>
<a-select-option :value="1">Random+Prefix</a-select-option>
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
</a-form-item>
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.xtls">
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="app.subSettings.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
Subscription
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
</a-form-item>
<a-form-item v-if="app.tgBotEnable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
Telegram ID
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
</template>
<span>{{ i18n "pages.inbounds.IPLimit" }} </span>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
<a-form-item v-else>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
</a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label">
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
</a-form-item>
</a-form>
</a-modal>
<script>
@@ -317,6 +236,9 @@
get delayedExpireDays() {
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
},
get datepicker() {
return app.datepicker;
},
set delayedExpireDays(days) {
this.clientsBulkModal.expiryTime = -86400000 * days;
},

View File

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

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="انتخاب تاریخ">
<template #addonAfter>
<a-icon type="calendar" style="font-size: 16px;"/>
</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: ['dropdown-class-name', '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,
container: '.ant-modal-wrap',
hideAfterChange: true,
useDropDownYears: false,
changeMonthRotateYear: true,
});
},
}
});
</script>
{{end}}

View File

@@ -1,223 +1,170 @@
{{define "form/client"}}
<a-form layout="inline" v-if="client">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.enable" }}</td>
<td>
<a-form-item>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.email" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.email" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<td>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.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
<td>
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
</td>
</tr>
<tr v-if="client.email && app.subSettings.enable">
<td>Subscription <a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon></td>
<td>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
</a-tooltip>
<a-form-item>
<a-input v-model.trim="client.subId" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && app.tgBotEnable">
<td>Telegram ID
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="client.email && client.limitIp > 0 && isEdit">
<td>
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</template>
<span style="color: #FF4D4F">
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
</span>
</a-tooltip>
</td>
<td>
<a-form layout="block">
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email)"
placeholder="Click To Get IPs" :auto-size="{ minRows: 5, maxRows: 10 }">
</a-textarea>
</a-form>
</td>
</tr>
<tr v-if="inbound.xtls">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="client.flow" style="width: 200px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-else-if="inbound.canEnableTlsFlow()">
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="client.flow" style="width: 200px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
<template v-if="isEdit && clientStats">
<br>
<span> {{ i18n "usage" }}:</span>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet"
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
v-if="client.email.length > 0"></a-icon>
</a-tooltip>
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
</template>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"
style="width: 170px;"></a-date-picker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item>
</td>
</tr>
<tr v-if="client.expiryTime != 0">
<td>
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
{{ i18n "pages.inbounds.email" }}
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.email"></a-input>
</a-form-item>
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ 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.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.password"></a-input>
</a-form-item>
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.id"></a-input>
</a-form-item>
<a-form-item v-if="client.email && app.subSettings.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
</template>
Subscription
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.subId"></a-input>
</a-form-item>
<a-form-item v-if="client.email && app.tgBotEnable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
</template>
Telegram ID
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.tgId"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitDesc"}}</span>
</template>
<span>{{ i18n "pages.inbounds.IPLimit"}} </span>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
</a-form-item>
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
</template>
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
</template>
<span style="color: #FF4D4F">
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
</span>
</a-tooltip>
<a-form layout="block">
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email)" placeholder="Click To Get IPs"
:auto-size="{ minRows: 5, maxRows: 10 }">
</a-textarea>
</a-form>
</a-form-item>
<a-form-item v-if="inbound.xtls" label='Flow'>
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet"
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
v-if="client.email.length > 0"></a-icon>
</a-tooltip>
</a-form-item>
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
<a-form-item v-else>
<template slot="label">
<a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item>
<a-form-item v-if="client.expiryTime != 0">
<template slot="label">
<a-tooltip>
<template slot="title">{{ i18n "pages.client.renewDesc" }}</template>
{{ i18n "pages.client.renew" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,93 +1,66 @@
{{define "form/inbound"}}
<!-- base -->
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "enable" }}</td>
<td>
<a-form-item>
<a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "remark" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "monitor" }}
<a-tooltip>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "enable" }}'>
<a-switch v-model="dbInbound.enable"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "remark" }}'>
<a-input v-model.trim="dbInbound.remark"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.port"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form>
{{ i18n "monitor" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.listen"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="inbound.port"></a-input-number>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
{{ i18n "pages.inbounds.totalFlow" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
{{ i18n "pages.inbounds.expireDate" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker>
<persian-datepicker v-else :dropdown-class-name="themeSwitcher.currentTheme"
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
</a-form-item>
</a-form>
<!-- vmess settings -->
<template v-if="inbound.protocol === Protocols.VMESS">
@@ -124,6 +97,11 @@
{{template "form/http"}}
</template>
<!-- wireguard -->
<template v-if="inbound.protocol === Protocols.WIREGUARD">
{{template "form/wireguard"}}
</template>
<!-- stream settings -->
<template v-if="inbound.canEnableStream()">
{{template "form/streamSettings"}}

View File

@@ -2,535 +2,386 @@
<!-- base -->
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
<a-tab-pane key="1" tab="Form">
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
<td>
<a-form-item has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
</a-form-item>
</td>
</tr>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "protocol" }}'>
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
</a-form-item>
<!-- freedom settings-->
<template v-if="outbound.protocol === Protocols.Freedom">
<tr>
<td>Strategy</td>
<td>
<a-form-item>
<a-select
v-model="outbound.settings.domainStrategy"
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Fragment</td>
<td>
<a-form-item>
<a-switch
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
</td>
</tr>
<a-form-item label='Strategy'>
<a-select
v-model="outbound.settings.domainStrategy"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Fragment'>
<a-switch
:checked="Object.keys(outbound.settings.fragment).length >0"
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
</a-switch>
</a-form-item>
<template v-if="Object.keys(outbound.settings.fragment).length >0">
<tr>
<td>Packets</td>
<td>
<a-form-item>
<a-select
v-model="outbound.settings.fragment.packets"
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Length</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Interval</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<a-form-item label='Packets'>
<a-select
v-model="outbound.settings.fragment.packets"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Length'>
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
</a-form-item>
<a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item>
</template>
</template>
<!-- blackhole settings -->
<template v-if="outbound.protocol === Protocols.Blackhole">
<tr>
<td>Response Type</td>
<td>
<a-form-item>
<a-select
v-model="outbound.settings.type"
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='Response Type'>
<a-select
v-model="outbound.settings.type"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- dns settings -->
<template v-if="outbound.protocol === Protocols.DNS">
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-form-item>
<a-select
v-model="outbound.settings.network"
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select
v-model="outbound.settings.network"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</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 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>
<!-- Address + Port -->
<template v-if="outbound.hasAddressPort()">
<tr>
<td>{{ i18n "pages.inbounds.address" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.port" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.inbounds.address" }}'>
<a-input v-model.trim="outbound.settings.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
</a-form-item>
</template>
<!-- Vnext (vless/vmess) settings -->
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
<tr>
<td>ID</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()">
<tr>
<td>Flow</td>
<td>
<a-form-item>
<a-select v-model="outbound.settings.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
<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 v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</template>
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
<template v-if="outbound.hasServers()">
<tr v-if="outbound.hasUsername()">
<td>{{ i18n "username" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<tr>
<td>{{ i18n "encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>UDP over TCP</td>
<td>
<a-form-item>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</td>
</tr>
<!-- http / socks -->
<template v-if="outbound.hasUsername()">
<a-form-item label='{{ i18n "username" }}'>
<a-input v-model.trim="outbound.settings.user"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='UDP over TCP'>
<a-switch v-model="outbound.settings.uot"></a-switch>
</a-form-item>
</template>
</template>
<!-- stream settings -->
<template v-if="outbound.canEnableStream()">
<tr>
<td>{{ i18n "transmission" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">KCP</a-select-option>
<a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">HTTP2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WS</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="grpc">gRPC</a-select-option>
</a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'">
<tr>
<td>http {{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-switch
:checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
</td>
</tr>
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch
:checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'">
<tr>
<td>{{ i18n "host" }}</td>
<td>
<a-form-item>
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item>
</template>
</template>
<!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'">
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>mtu</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>tti (ms)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uplink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model="outbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</template>
<!-- ws -->
<template v-if="outbound.stream.network === 'ws'">
<tr>
<td>{{ i18n "host" }}</td>
<td><a-form-item><a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input></a-form-item></td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td><a-form-item><a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input></a-form-item></td>
</tr>
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.ws.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item>
</template>
<!-- http -->
<template v-if="outbound.stream.network === 'http'">
<tr>
<td>{{ i18n "host" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.http.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.http.path"></a-input>
</a-form-item>
</template>
<!-- quic -->
<template v-if="outbound.stream.network === 'quic'">
<tr>
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none</a-select-option>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'">
<tr>
<td>serviceName</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>MultiMode</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</td>
</tr>
<a-form-item label='Service Name'>
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
</a-form-item>
<a-form-item label='Multi Mode'>
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</template>
</template>
<!-- tls settings -->
<template v-if="outbound.canEnableTls()">
<tr>
<td>{{ i18n "security" }}</td>
<td>
<a-form-item>
<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="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group>
</a-form-item>
</td>
</tr>
<template v-if="outbound.stream.isTls">
<tr>
<td>SNI</td>
<td>
<a-form-item placeholder="Server Name Indication">
<a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.tls.fingerprint"
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>ALPN</td>
<td>
<a-form-item>
<a-select
mode="multiple"
style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
</template>
<!-- reality settings -->
<template v-if="outbound.stream.isReality">
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item>
<a-select v-model="outbound.stream.reality.fingerprint"
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Short Id</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>SpiderX</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
</template>
<a-form-item label='{{ i18n "security" }}'>
<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="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">REALITY</a-radio-button>
</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 label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
</a-form-item>
</template>
<!-- reality settings -->
<template v-if="outbound.stream.isReality">
<a-form-item label='{{ i18n "domainName" }}'>
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Short ID">
<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>
</table>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true">
@@ -541,4 +392,4 @@
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
</a-tab-pane>
</a-tabs>
{{end}}
{{end}}

View File

@@ -1,42 +1,20 @@
{{define "form/dokodemo"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.targetAddress"}}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.address"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.destinationPort"}}</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network"}}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>FollowRedirect</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
<a-input v-model.trim="inbound.settings.address"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Follow Redirect'>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,5 +1,5 @@
{{define "form/http"}}
<a-form layout="inline">
<a-form>
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>

View File

@@ -1,6 +1,5 @@
{{define "form/shadowsocks"}}
<a-form layout="inline">
<template v-if="inbound.isSSMultiUser">
<template v-if="inbound.isSSMultiUser">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
@@ -20,40 +19,29 @@
</table>
</a-collapse-panel>
</a-collapse>
</template>
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</template>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="inbound.isSS2022">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template> Password <a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.password"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,52 +1,33 @@
{{define "form/socks"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td style="width: 30%;">{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.settings.auth === 'password'">
<td colspan="2">
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="inbound.settings.udp">
<td>IP</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
<a-switch v-model="inbound.settings.udp"></a-switch>
</a-form-item>
<a-form-item label="IP" v-if="inbound.settings.udp">
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "password" }}'>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
<template v-if="inbound.settings.auth === 'password'">
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
<tr>
<td width="45%">{{ i18n "username" }}</td>
<td width="45%">{{ i18n "password" }}</td>
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
</tr>
</table>
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -1,30 +1,29 @@
{{define "form/trojan"}}
<a-form layout="inline" style="padding: 10px 0px;">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>Password</th>
</tr>
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.password ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
</a-form>
<template v-if="inbound.isTcp">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>Password</th>
</tr>
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.password ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form layout="inline">
<a-form-item label="Fallbacks">
<a-row>
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">
<a-button type="primary" size="small"
@click="inbound.settings.addFallback()">
+
</a-button>
</a-row>
@@ -32,55 +31,28 @@
</a-form>
<!-- trojan fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
fallback[[ index + 1 ]]
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<table width="100%">
<tr>
<td style="width: 20%;">Name</td>
<td>
<a-form-item>
<a-input v-model="fallback.name" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Alpn</td>
<td>
<a-form-item>
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Path</td>
<td>
<a-form-item>
<a-input v-model="fallback.path" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>xVer</td>
<td>
<a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:0;"></a-divider>
</template>
{{end}}
{{end}}

View File

@@ -1,88 +1,60 @@
{{define "form/vless"}}
<a-form layout="inline" style="padding: 10px 0px;">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>Flow</th>
<th>ID</th>
</tr>
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.flow ]]</td>
<td>[[ client.id ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
</a-form>
<template v-if="inbound.isTcp">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>Flow</th>
<th>ID</th>
</tr>
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.flow ]]</td>
<td>[[ client.id ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
<template v-if="inbound.isTcp && !inbound.stream.isReality">
<a-form layout="inline">
<a-form-item label="Fallbacks">
<a-row>
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">
<a-button type="primary" size="small"
@click="inbound.settings.addFallback()">
+
</a-button>
</a-row>
</a-form-item>
</a-form>
<!-- vless fallbacks -->
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:0;">
fallback[[ index + 1 ]]
Fallback [[ index + 1 ]]
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
style="color: rgb(255, 77, 79);cursor: pointer;"/>
</a-divider>
<table width="100%">
<tr>
<td style="width: 20%;">Name</td>
<td>
<a-form-item>
<a-input v-model="fallback.name" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Alpn</td>
<td>
<a-form-item>
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Path</td>
<td>
<a-form-item>
<a-input v-model="fallback.path" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>xVer</td>
<td>
<a-form-item>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
<a-form-item label='SNI'>
<a-input v-model="fallback.name"></a-input>
</a-form-item>
<a-form-item label='ALPN'>
<a-input v-model="fallback.alpn"></a-input>
</a-form-item>
<a-form-item label='Path'>
<a-input v-model="fallback.path"></a-input>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model="fallback.dest"></a-input>
</a-form-item>
<a-form-item label='xVer'>
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
</a-form-item>
</a-form>
<a-divider style="margin:0;"></a-divider>
<a-divider style="margin:5px 0;"></a-divider>
</template>
{{end}}

View File

@@ -1,23 +1,21 @@
{{define "form/vmess"}}
<a-form layout="inline" style="padding: 10px 0px;">
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>ID</th>
</tr>
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.id ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
</a-form>
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
{{template "form/client"}}
</a-collapse-panel>
</a-collapse>
<a-collapse v-else>
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
<table width="100%">
<tr class="client-table-header">
<th>{{ i18n "pages.inbounds.email" }}</th>
<th>ID</th>
</tr>
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
<td>[[ client.email ]]</td>
<td>[[ client.id ]]</td>
</tr>
</table>
</a-collapse-panel>
</a-collapse>
{{end}}

View File

@@ -0,0 +1,56 @@
{{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 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>
</a-form>
{{end}}

View File

@@ -1,21 +1,22 @@
{{define "form/sniffing"}}
<a-form layout="inline">
<a-form-item>
<span slot="label">
Sniffing
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
</a-form-item>
<a-form-item>
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-divider style="margin:5px 0 0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item>
<span slot="label">
Sniffing
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</span>
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,32 +1,26 @@
{{define "form/externalProxy"}}
<a-form layout="inline">
<a-divider style="margin:0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:5px 0 0;"></a-divider>
<a-form-item label="External Proxy">
<a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
<a-switch v-model="externalProxy"></a-switch>
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
</a-form-item>
<table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
<tr style="line-height: 40px;">
<td width="100%">
<a-input-group style="margin: 0 5px;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template>
<a-tooltip title="Force TLS">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
<a-select-option value="tls">TLS</a-select-option>
</a-select>
</a-tooltip>
</template>
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip>
<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-input-group>
</td>
</tr>
</table>
<a-input-group style="margin: 8px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
<template>
<a-tooltip title="Force TLS">
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
<a-select-option value="tls">TLS</a-select-option>
</a-select>
</a-tooltip>
</template>
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip>
<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-input-group>
</a-form>
{{end}}

View File

@@ -1,22 +1,10 @@
{{define "form/streamGRPC"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>serviceName</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>MultiMode</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Service Name">
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
</a-form-item>
<a-form-item label="Multi Mode">
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,24 +1,19 @@
{{define "form/streamHTTP"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "path" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>host</td>
<td>
<a-form-item>
<a-row v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.http.path"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">{{ i18n "host" }}
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
</template>
<template v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]">
<a-button size="small" slot="addonAfter"
@click="inbound.stream.http.removeHost(index)"
v-if="inbound.stream.http.host.length>1">-</a-button>
</a-input>
</template>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,86 +1,47 @@
{{define "form/streamKCP"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None (Not Camouflage)</a-select-option>
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
<a-select-option value="wechat-video">Wechat-Video (Camouflage WeChat Video)</a-select-option>
<a-select-option value="dtls">DTLS (Camouflage DTLS 1.2 Packages)</a-select-option>
<a-select-option value="wireguard">Wireguard (Camouflage Wireguard Packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>MTU</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>TTI (ms)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>Uplink Capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>Downlink Capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>Congestion</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Read Buffer Size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>Write Buffer Size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item>
<a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
</a-form-item>
<a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
<a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
<a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
<a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
<a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
<a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,43 +1,33 @@
{{define "form/streamQUIC"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.security" style="width: 250px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none</a-select-option>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 280px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (camouflage WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (camouflage DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (camouflage wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
<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-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "password" }}
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option>
<a-select-option value="srtp">SRTP</a-select-option>
<a-select-option value="utp">uTP</a-select-option>
<a-select-option value="wechat-video">WeChat</a-select-option>
<a-select-option value="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option>
</a-select>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,11 +1,11 @@
{{define "form/streamSettings"}}
<!-- select stream network -->
<a-form layout="inline">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme" style="width: 100px;">
:dropdown-class-name="themeSwitcher.currentTheme">
<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="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option>
@@ -43,9 +43,8 @@
<template v-if="inbound.stream.network === 'grpc'">
{{template "form/streamGRPC"}}
</template>
<!-- sockopt -->
<template>
{{template "form/streamSockopt"}}
</template>
{{end}}
{{end}}

View File

@@ -1,46 +1,26 @@
{{define "form/streamSockopt"}}
<a-form layout="inline">
<a-form-item label="Transparent Proxy">
<a-divider style="margin:5px 0 0;"></a-divider>
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="TPROXY">
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
</a-form-item>
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
<tr>
<td>Accept Proxy Protocol</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>TCP FastOpen</td>
<td>
<a-form-item>
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Route Mark</td>
<td>
<a-form-item>
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>T-Proxy</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="off">OFF</a-select-option>
<a-select-option value="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">T-Proxy</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
<template v-if="inbound.stream.sockoptSwitch">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Route Mark">
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TPROXY">
<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="redirect">Redirect</a-select-option>
<a-select-option value="tproxy">TPROXY</a-select-option>
</a-select>
</a-form-item>
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -1,113 +1,82 @@
{{define "form/streamTCP"}}
<!-- tcp type -->
<a-form layout="inline">
<a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol" v-if="inbound.canEnableTls()">
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch
:checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
<a-switch :checked="inbound.stream.tcp.type === 'http'"
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch>
</a-form-item>
</a-form>
<!-- tcp request -->
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td style="vertical-align: top; padding-top: 10px;">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
</td>
<td>
<a-form-item>
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;">
<a-button size="small" slot="addonAfter"
@click="inbound.stream.tcp.request.removePath(index)"
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
</a-input>
</a-row>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2" width="100%">
<a-form-item>
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
<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" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<!-- tcp response -->
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2" width="100%">
<a-form-item>
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
<a-button size="small" style="margin-left: 10px"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
<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" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
:wrapper-col="{ md: {span:14} }">
<!-- 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-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
</template>
<template v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
</a-input>
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('host', '')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
<a-input style="width: 50%" v-model.trim="header.name"
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small"
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<!-- 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-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'>
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-button size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<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" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value"
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}

View File

@@ -1,30 +1,23 @@
{{define "form/streamWS"}}
<a-form layout="inline">
<a-form-item label="AcceptProxyProtocol">
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
</a-form-item>
<br>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item>
<br>
<a-form-item>
<a-row>
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
<a-button type="primary" size="small" style="margin-left: 10px"
@click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button type="primary" size="small" style="margin-left: 10px"
@click="inbound.stream.ws.removeHeader(index)">-</a-button>
</template>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}
{{end}}

View File

@@ -1,395 +1,194 @@
{{define "form/tlsSettings"}}
<!-- tls enable -->
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-divider style="margin:3px 0;"></a-divider>
<a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="inbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
</template>
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
</a-tooltip>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
</template>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">REALITY</a-radio-button>
</a-tooltip>
<a-radio-button value="tls">TLS</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form v-if="inbound.canEnableTls()" layout="inline">
<a-divider style="margin:0;"></a-divider>
<table width="100%" class="ant-table-tbody">
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "security" }}'>
<a-radio-group v-model="inbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
</template>
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
</a-tooltip>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
</template>
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-tooltip>
<a-radio-button value="tls">TLS</a-radio-button>
</a-radio-group>
<!-- tls settings -->
<template v-if="inbound.stream.isTls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
</a-form-item>
<a-form-item label="Cipher Suites">
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">Auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Min/Max Version">
<a-input-group compact>
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="inbound.stream.tls.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
<a-form-item label="Reject Unknown SNI">
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
<a-form-item label='OCSP stapling'>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</template>
</template>
<!-- tls settings -->
<template v-if="inbound.stream.isTls">
<tr>
<td>SNI</td>
<td>
<a-form-item placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.tls.sni" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>CipherSuites</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="">auto</a-select-option>
<a-select-option v-for="key,value in TLS_CIPHER_OPTION"
:value="key">[[ value]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Min/Max Version</td>
<td>
<a-form-item>
<a-input-group compact>
<a-select style="width: 125px" v-model="inbound.stream.tls.minVersion"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION"
:value="key">[[ key ]]</a-select-option>
</a-select>
<a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in TLS_VERSION_OPTION"
:value="key">[[ key ]]</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT"
:value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>ALPN</td>
<td>
<a-form-item>
<a-select mode="multiple" style="width: 250px"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION"
:value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Reject Unknown SNI</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
</a-form-item>
</td>
</tr>
<template v-for="cert,index in inbound.stream.tls.certs">
<tr>
<td>{{ i18n "certificate" }}</td>
<td>
<a-form-item>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button
:value="true">{{ i18n "pages.inbounds.certificatePath"}}
</a-radio-button>
<a-radio-button
:value="false">{{ i18n "pages.inbounds.certificateContent"}}
</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small"
@click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
</td>
</tr>
<template v-if="cert.useFile">
<tr>
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">
{{ i18n "pages.inbounds.setDefaultCert" }}
</a-button>
</td>
</tr>
</template>
<template v-else>
<tr>
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</td>
</tr>
</template>
<tr>
<td>ocspStapling</td>
<td>
<a-form-item>
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<!-- xtls settings -->
<template v-else-if="inbound.xtls">
<a-form-item label="SNI" placeholder="Server Name Indication">
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.xtls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Allow Insecure">
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
</a-form-item>
<template v-for="cert,index in inbound.stream.xtls.certs">
<a-form-item label='{{ i18n "certificate" }}'>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
<template v-if="cert.useFile">
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
<a-input v-model.trim="cert.certFile"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
<a-input v-model.trim="cert.keyFile"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</a-form-item>
</template>
<template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
</a-form-item>
</template>
</template>
<!-- xtls settings -->
<template v-else-if="inbound.xtls">
<tr>
<td>
<span>SNI</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.xtls.server" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Alpn</span>
</td>
<td>
<a-form-item>
<a-select mode="multiple" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="inbound.stream.xtls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Allow insecure</span>
</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<template v-for="cert,index in inbound.stream.xtls.certs">
<tr>
<td>{{ i18n "certificate" }}</td>
<td>
<a-form-item>
<a-radio-group v-model="cert.useFile" button-style="solid">
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath"
}}</a-radio-button>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent"
}}</a-radio-button>
</a-radio-group>
<a-button v-if="index === 0" type="primary" size="small"
@click="inbound.stream.xtls.addCert()" style="margin-left: 10px">+</a-button>
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
</a-form-item>
</td>
</tr>
<template v-if="cert.useFile">
<tr>
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
"pages.inbounds.setDefaultCert" }}</a-button>
</td>
</tr>
</template>
<template v-else>
<tr>
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
</a-form-item>
</td>
</tr>
</template>
</template>
<!-- reality settings -->
<template v-if="inbound.stream.isReality">
<a-form-item label='Show'>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
<a-form-item label='Xver'>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
<a-form-item label='uTLS'>
<a-select v-model="inbound.stream.reality.settings.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Dest'>
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
</a-form-item>
<a-form-item label='SNI'>
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
Short ID
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
</a-icon>
</template>
</template>
<!-- reality settings -->
<template v-else-if="inbound.reality">
<tr>
<td>
<span>Show</span>
</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>xVer</span>
</td>
<td>
<a-form-item>
<a-input-number v-model="inbound.stream.reality.xver" :min="0"
style="width: 60px"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>uTLS</span>
</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 135px"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Dest</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Server Names</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>ShortIds</span> <a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()"
type="sync"> </a-icon>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width: 150px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>SpiderX</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width: 150px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Private Key</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span>Public Key</span>
</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"
style="width: 300px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
</td>
</tr>
</template>
</table>
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
</a-form-item>
<a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</a-form-item>
<a-form-item label='Private Key'>
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
</a-form-item>
<a-form-item label='Public Key'>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
</a-form-item>
</template>
</a-form>
{{end}}
{{end}}

View File

@@ -19,7 +19,7 @@
:overlay-class-name="themeSwitcher.currentTheme"
ok-text='{{ i18n "reset"}}'
cancel-text='{{ i18n "cancel"}}'>
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #3c89e8' : 'color: blue'"></a-icon>
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
</a-popconfirm>
</a-tooltip>

View File

@@ -3,72 +3,89 @@
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
:closable="true"
:mask-closable="true"
:class="themeSwitcher.currentTheme"
:footer="null"
width="600px"
:class="themeSwitcher.currentTheme"
>
<table style="margin-bottom: 10px; width: 100%;">
<tr><td>
<table>
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag>[[ dbInbound.address ]]</a-tag></td></tr>
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
</table>
</td>
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table>
<tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
</tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host"><a-tag>[[ inbound.host ]]</a-tag></td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path"><a-tag>[[ inbound.path ]]</a-tag></td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
</tr>
</template>
<template v-if="inbound.isQuic">
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td><a-tag>[[ inbound.serviceName ]]</a-tag></td></tr>
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
</template>
</table>
</td></tr>
<tr colspan="2" v-if="dbInbound.hasLink()">
<td>
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'blue'">[[ inbound.stream.security ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'blue' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</template>
</td>
</tr>
</table>
<a-row>
<a-col :xs="24" :md="12">
<table>
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td>
<a-tooltip :title="[[ dbInbound.address ]]">
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
</a-tooltip>
</td></tr>
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
</table>
</a-col>
<a-col :xs="24" :md="12">
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
<table>
<tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
</tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
<tr>
<td>{{ i18n "host" }}</td>
<td v-if="inbound.host">
<a-tooltip :title="[[ inbound.host ]]">
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
</a-tooltip>
</td>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
</tr>
<tr>
<td>{{ i18n "path" }}</td>
<td v-if="inbound.path">
<a-tooltip :title="[[ inbound.path ]]">
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
</a-tooltip>
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
</tr>
</template>
<template v-if="inbound.isQuic">
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isKcp">
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
</template>
<template v-if="inbound.isGrpc">
<tr><td>grpc serviceName</td><td>
<a-tooltip :title="[[ inbound.serviceName ]]">
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
</a-tooltip>
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
</template>
</table>
</template>
</a-col>
<template v-if="dbInbound.hasLink()">
{{ i18n "security" }}
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
<br />
<template v-if="inbound.stream.security != 'none'">
{{ i18n "domainName" }}
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</template>
</template>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr>
<td>{{ i18n "encryption" }}</td>
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
</tr><tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td>
<td><a-tag>[[ inbound.settings.password ]]</a-tag></td>
<td>
<a-tooltip :title="[[ inbound.settings.password ]]">
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
</a-tooltip>
</td>
</tr><tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
@@ -89,9 +106,17 @@
<td>Flow</td>
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
</tr>
<tr v-if="infoModal.inbound.xtls">
<td>Flow</td>
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
</tr>
<tr v-if="infoModal.clientSettings.password">
<td>Password</td>
<td><a-tag>[[ infoModal.clientSettings.password ]]</a-tag></td>
<td>{{ i18n "password" }}</td>
<td>
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
<a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag>
</a-tooltip>
</td>
</tr>
<tr>
<td>{{ i18n "status" }}</td>
@@ -115,69 +140,69 @@
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
</tr>
<tr>
<td>
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
</a-tag>
</td>
<td>
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
</a-tag>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
</td>
<td>
<template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
<tr>
<td>
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
</a-tag>
</template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
</td>
</tr>
</table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription link</a-divider>
<a-row>
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :span="2">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</td>
<td>
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
</a-tag>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
</td>
<td>
<template v-if="infoModal.clientSettings.expiryTime > 0">
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</a-tag>
</template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
<a-tag v-else color="purple" class="infinite-tag">&infin;</a-tag>
</td>
</tr>
</table>
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
<a-divider>Subscription URL</a-divider>
<a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
<a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider>
<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="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<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>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
<template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links">
<a-col :sx="24" :md="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
<a-col :sx="24" :md="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, link.link)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
</template>
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram Username</a-divider>
<a-row>
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
<a-col :span="2">
<a-tooltip title='{{ i18n "copy" }}'>
<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>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
<template v-if="dbInbound.hasLink()">
<a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links">
<a-col :span="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</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, link.link)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</template>
</template>
<template v-else>
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider>
@@ -217,7 +242,7 @@
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
</tr>
<template v-if="inbound.settings.auth == 'password'">
<tr>
<tr>
<td> </td>
<td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td>
@@ -226,9 +251,9 @@
<td><a-tag color="green">[[ account.user ]]</a-tag></td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr>
</template>
</table>
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
</template>
</table>
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
<tr>
<th> </th>
<th>{{ i18n "username" }}</th>
@@ -239,6 +264,45 @@
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr>
</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-tag>Peer [[ index + 1 ]]</a-tag></td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ peer.publicKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<td>[[ peer.psk ]]</td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<td>[[ peer.allowedIPs.join(",") ]]</td>
</tr>
<tr>
<td>Keep Alive</td>
<td>[[ peer.keepAlive ]]</td>
</tr>
</table>
</template>
</template>
</a-modal>
<script>
@@ -246,7 +310,6 @@
visible: false,
inbound: new Inbound(),
dbInbound: new DBInbound(),
settings: null,
clientSettings: null,
clientStats: [],
upStats: 0,
@@ -279,7 +342,7 @@
infoModal.visible = false;
},
genSubLink(subID) {
return app.subSettings.subURI+subID+'?name='+subID;
return app.subSettings.subURI+subID;
}
};
@@ -295,24 +358,24 @@
return this.infoModal.inbound;
},
get isActive() {
if (infoModal.clientStats) {
if(infoModal.clientStats){
return infoModal.clientStats.enable;
}
return true;
},
get isEnable() {
if (infoModal.clientSettings) {
if(infoModal.clientSettings){
return infoModal.clientSettings.enable;
}
return infoModal.dbInbound.isEnable;
},
},
methods: {
copyToClipboard(elmentId, content) {
copyToClipboard(elmentId,content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {
text: () => content,
});
this.infoModal.clipboard.on('success', () => {
app.$message.success('{{ i18n "copied" }}')
this.infoModal.clipboard.destroy();
});
@@ -321,7 +384,8 @@
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
}
},
});
</script>
{{end}}
{{end}}

View File

@@ -63,6 +63,9 @@
get client() {
return inModal.inbound.clients[0];
},
get datepicker() {
return app.datepicker;
},
get delayedExpireDays() {
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
},
@@ -98,6 +101,11 @@
client.flow = "";
});
}
if ((this.inModal.inbound.protocol == Protocols.VLESS || this.inModal.inbound.protocol == Protocols.TROJAN) && !inModal.inbound.xtls) {
this.inModal.inbound.settings.vlesses.forEach(client => {
client.flow = "";
});
}
},
SSMethodChange() {
if (this.inModal.inbound.isSSMultiUser) {

View File

@@ -43,6 +43,10 @@
0%, 50%, 100% { transform: scale(1); opacity: 1; }
10% { transform: scale(1.5); opacity: .2; }
}
.info-large-tag {
max-width: 200px;
overflow: hidden;
}
</style>
<body>
@@ -157,9 +161,9 @@
</a-col>
</a-row>
</div>
<div style="display: flex; align-items: center; justify-content: flex-start;">
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
<a-switch v-model="enableFilter"
style="margin-right: .5rem;"
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
@change="toggleFilter">
<a-icon slot="checkedChildren" type="search"></a-icon>
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
@@ -240,7 +244,7 @@
</span>
</a-menu-item>
<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" }}
</a-menu-item>
</a-menu>
@@ -310,7 +314,7 @@
</a-popover>
</template>
<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 slot="expiryTime" slot-scope="text, dbInbound">
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
@@ -419,11 +423,11 @@
</template>
<template slot="expandedRowRender" slot-scope="record">
<a-table
:row-key="client => client.id"
:columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record))
:style="isMobile ? 'margin: -12px -5px -13px;' : 'margin-left: 10px;'">
:row-key="client => client.id"
:columns="isMobile ? innerMobileColumns : innerColumns"
:data-source="getInboundClients(record)"
:pagination=pagination(getInboundClients(record))
:style="isMobile ? 'margin: -12px 2px -13px;' : 'margin: -12px 22px -13px;'">
{{template "client_table"}}
</a-table>
</template>
@@ -442,27 +446,28 @@
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}}
{{template "component/persianDatepicker" .}}
<script>
const columns = [{
title: "ID",
align: 'right',
dataIndex: "id",
width: 40,
width: 30,
responsive: ["xs"],
}, {
title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center',
width: 40,
width: 30,
scopedSlots: { customRender: 'action' },
}, {
title: '{{ i18n "pages.inbounds.enable" }}',
align: 'center',
width: 40,
width: 30,
scopedSlots: { customRender: 'enable' },
}, {
title: '{{ i18n "pages.inbounds.remark" }}',
align: 'center',
width: 80,
width: 60,
dataIndex: "remark",
}, {
title: '{{ i18n "pages.inbounds.port" }}',
@@ -472,7 +477,7 @@
}, {
title: '{{ i18n "pages.inbounds.protocol" }}',
align: 'left',
width: 90,
width: 70,
scopedSlots: { customRender: 'protocol' },
}, {
title: '{{ i18n "clients" }}',
@@ -487,7 +492,7 @@
}, {
title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center',
width: 60,
width: 40,
scopedSlots: { customRender: 'expiryTime' },
}];
@@ -535,6 +540,7 @@
data: {
siderDrawer,
themeSwitcher,
persianDatepicker,
spinning: false,
inbounds: [],
dbInbounds: [],
@@ -556,6 +562,7 @@
subURI : ''
},
remarkModel: '-ieo',
datepicker: 'gregorian',
tgBotEnable: false,
pageSize: 0,
isMobile: window.innerWidth <= 768,
@@ -601,6 +608,7 @@
};
this.pageSize = pageSize;
this.remarkModel = remarkModel;
this.datepicker = datepicker;
}
},
setInbounds(dbInbounds) {
@@ -1026,8 +1034,9 @@
newDbInbound = this.checkFallback(dbInbound);
infoModal.show(newDbInbound, index);
},
switchEnable(dbInboundId) {
switchEnable(dbInboundId,state) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
dbInbound.enable = state;
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
},
async switchEnableClient(dbInboundId, client) {
@@ -1089,7 +1098,7 @@
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
class: themeSwitcher.currentTheme,
okText: '{{ i18n "reset"}}',
okText: '{{ i18n "delete"}}',
cancelText: '{{ i18n "cancel"}}',
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
})
@@ -1175,7 +1184,7 @@
inboundLinks(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
},
importInbound() {
promptModal.open({
@@ -1306,4 +1315,4 @@
{{template "clientsBulkModal"}}
</body>
</html>
</html>

View File

@@ -77,8 +77,8 @@
<a-row>
<a-col :sm="24" :md="12">
<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>
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
3X-UI <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
Xray <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
</a-card>
</a-col>
@@ -93,16 +93,16 @@
<a-col :sm="24" :md="12">
<a-card hoverable>
{{ i18n "pages.index.xrayStatus" }}:
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
<a-tag :color="status.xray.color">[[ status.xray.state.toUpperCase() ]]</a-tag>
<a-popover v-if="status.xray.state === State.Error"
: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>
</span>
<template slot="content">
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</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="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
@@ -112,9 +112,9 @@
<a-col :sm="24" :md="12">
<a-card hoverable>
{{ i18n "pages.index.operationHours" }}:
Xray:
Xray
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
OS:
OS
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
</a-card>
</a-col>
@@ -125,14 +125,14 @@
<template slot="title">
{{ i18n "pages.index.systemLoadDesc" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable>
{{ i18n "usage"}}:
Memory [[ sizeFormat(status.appStats.mem) ]] -
RAM [[ sizeFormat(status.appStats.mem) ]] -
Threads [[ status.appStats.threads ]]
</a-tooltip>
</a-card>
@@ -141,21 +141,23 @@
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="global"></a-icon>
IPv4:
<a-tooltip>
<template slot="title">
[[ status.publicIP.ipv4 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
<a-col :span="12">
<a-icon type="global"></a-icon>
IPv6:
<a-tooltip>
<template slot="title">
[[ status.publicIP.ipv6 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
</a-row>
@@ -165,21 +167,23 @@
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="swap"></a-icon>
TCP: [[ status.tcpCount ]]
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.connectionTcpCountDesc" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
<a-col :span="12">
<a-icon type="swap"></a-icon>
UDP: [[ status.udpCount ]]
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.connectionUdpCountDesc" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
</a-row>
@@ -190,22 +194,22 @@
<a-row>
<a-col :span="12">
<a-icon type="arrow-up"></a-icon>
[[ sizeFormat(status.netIO.up) ]]/S
[[ sizeFormat(status.netIO.up) ]]/s
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.upSpeed" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
<a-col :span="12">
<a-icon type="arrow-down"></a-icon>
[[ sizeFormat(status.netIO.down) ]]/S
[[ sizeFormat(status.netIO.down) ]]/s
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.downSpeed" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
</a-row>
@@ -221,7 +225,7 @@
<template slot="title">
{{ i18n "pages.index.totalSent" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
<a-col :span="12">
@@ -231,7 +235,7 @@
<template slot="title">
{{ i18n "pages.index.totalReceive" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-col>
</a-row>
@@ -246,8 +250,10 @@
:closable="true" @ok="() => versionModal.visible = false"
:class="themeSwitcher.currentTheme"
footer="">
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
message='{{ i18n "pages.index.xraySwitchClickDesk" }}'
show-icon
></a-alert>
<template v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
style="margin: 10px" @click="switchV2rayVersion(version)">
@@ -650,4 +656,4 @@
</script>
</body>
</html>
</html>

View File

@@ -138,6 +138,27 @@
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.settings.datepicker"}}'>
<template slot="description">{{ i18n "pages.settings.datepickerDescription"}}</template>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="datepicker">
<a-select-option v-for="item in datepickerList" :value="item.value">
<span v-text="item.name"></span>
</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
@@ -166,42 +187,22 @@
</a-tab-pane>
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
<a-form style="padding: 20px;" layout="inline">
<table cellpadding="2">
<tr>
<td>{{ i18n "pages.settings.oldUsername"}}:</td>
<td>
<a-form-item>
<a-input v-model="user.oldUsername" style="width: 200px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.settings.currentPassword"}}:</td>
<td>
<a-form-item>
<password-input v-model="user.oldPassword" style="width: 200px"></password-input>
</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 layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
<a-input v-model="user.oldUsername"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
<password-input v-model="user.oldPassword"></password-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
<a-input v-model="user.newUsername"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
<password-input v-model="user.newPassword"></password-input>
</a-form-item>
<a-form-item label=" ">
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
</a-form-item>
</a-form>
<a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider>
<a-form style="padding: 20px;">
@@ -243,6 +244,7 @@
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramProxy"}}' desc='{{ i18n "pages.settings.telegramProxyDesc"}}' v-model="allSetting.tgBotProxy" placeholder="socks5://[user:pass@]host:port"></setting-list-item>
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
@@ -311,6 +313,7 @@
showAlert: false,
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
remarkSample: '',
get remarkModel() {
rm = this.allSetting.remarkModel;
@@ -328,6 +331,12 @@
this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1);
this.changeRemarkSample();
},
get datepicker() {
return this.allSetting.datepicker ? this.allSetting.datepicker : 'gregorian';
},
set datepicker(value) {
this.allSetting.datepicker = value;
},
changeRemarkSample(){
sample = []
this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
@@ -384,9 +393,7 @@
if (msg.success) {
this.loading(true);
await PromiseUtil.sleep(5000);
var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
if (host == this.oldAllSetting.webDomain) host = null;
if (port == this.oldAllSetting.webPort) port = null;
let { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
const isTLS = webCertFile !== "" || webKeyFile !== "";
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
window.location.replace(url);

View File

@@ -0,0 +1,204 @@
{{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) {
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),
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}}

View File

@@ -3,10 +3,11 @@
{{template "head" .}}
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/codemirror.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/fold/foldgutter.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.css?{{ .cur_ver }}">
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
<script src="{{ .base_path }}assets/js/model/outbound.js"></script>
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
<script src="{{ .base_path }}assets/js/model/outbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/codemirror/codemirror.js"></script>
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
<script src="{{ .base_path }}assets/codemirror/jshint.js"></script>
@@ -29,6 +30,10 @@
margin: 0;
padding: 12px .5rem;
}
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 10px 0px;
}
}
.ant-tabs-bar {
@@ -71,22 +76,26 @@
<template slot="content">
<p style="max-width: 400px" v-for="line in restartResult.split('\n')">[[ line ]]</p>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
<a-icon type="question-circle"></a-icon>
</a-popover>
</a-space>
</a-col>
<a-col :xs="24" :sm="16">
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
</a-back-top>
<a-alert type="warning" style="float: right; width: fit-content"
message='{{ i18n "pages.settings.infoDesc" }}'
show-icon
>
<template>
<div>
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
</a-back-top>
<a-alert type="warning" style="float: right; width: fit-content"
message='{{ i18n "pages.settings.infoDesc" }}'
show-icon
>
</div>
</template>
</a-col>
</a-row>
</a-card>
<a-tabs class="ant-card-dark-box-nohover" default-active-key="tpl-1"
@change="(activeKey) => { if(activeKey == 'tpl-4') this.changeCode(); }"
@change="(activeKey) => { if(activeKey == 'tpl-advanced') this.changeCode(); }"
:class="themeSwitcher.currentTheme">
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.xray.basicTemplate"}}'>
<a-space direction="horizontal" style="padding: 20px 20px">
@@ -106,8 +115,8 @@
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.xrayConfigFreedomStrategy" }}'
description='{{ i18n "pages.xray.xrayConfigFreedomStrategyDesc" }}'/>
title='{{ i18n "pages.xray.FreedomStrategy" }}'
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
@@ -115,7 +124,7 @@
v-model="freedomStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<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>
</template>
</a-col>
@@ -124,8 +133,8 @@
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.xrayConfigRoutingStrategy" }}'
description='{{ i18n "pages.xray.xrayConfigRoutingStrategyDesc" }}'/>
title='{{ i18n "pages.xray.RoutingStrategy" }}'
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
</a-col>
<a-col :lg="24" :xl="12">
<template>
@@ -149,11 +158,12 @@
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigTorrent"}}' desc='{{ i18n "pages.xray.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.xray.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigAds"}}' desc='{{ i18n "pages.xray.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigFamily"}}' desc='{{ i18n "pages.xray.xrayConfigFamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigSpeedtest"}}' desc='{{ i18n "pages.xray.xrayConfigSpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Torrent"}}' desc='{{ i18n "pages.xray.TorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.PrivateIp"}}' desc='{{ i18n "pages.xray.PrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Ads"}}' desc='{{ i18n "pages.xray.AdsDesc"}}' v-model="AdsSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Family"}}' desc='{{ i18n "pages.xray.FamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Security"}}' desc='{{ i18n "pages.xray.SecurityDesc"}}' v-model="SecuritySettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.Speedtest"}}' desc='{{ i18n "pages.xray.SpeedtestDesc"}}' v-model="SpeedTestSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.blockCountryConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
@@ -164,12 +174,14 @@
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigIRIp"}}' desc='{{ i18n "pages.xray.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigIRDomain"}}' desc='{{ i18n "pages.xray.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigChinaIp"}}' desc='{{ i18n "pages.xray.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.xray.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRIp"}}' desc='{{ i18n "pages.xray.IRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRDomain"}}' desc='{{ i18n "pages.xray.IRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaIp"}}' desc='{{ i18n "pages.xray.ChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaDomain"}}' desc='{{ i18n "pages.xray.ChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaIp"}}' desc='{{ i18n "pages.xray.RussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaDomain"}}' desc='{{ i18n "pages.xray.RussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.VNIp"}}' desc='{{ i18n "pages.xray.VNIpDesc"}}' v-model="VNIpSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.VNDomain"}}' desc='{{ i18n "pages.xray.VNDomainDesc"}}' v-model="VNDomainSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
@@ -180,12 +192,14 @@
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectIRIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectIRDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectChinaIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectChinaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectRussiaIp"}}' desc='{{ i18n "pages.xray.xrayConfigDirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigDirectRussiaDomain"}}' desc='{{ i18n "pages.xray.xrayConfigDirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRIp"}}' desc='{{ i18n "pages.xray.DirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRDomain"}}' desc='{{ i18n "pages.xray.DirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaIp"}}' desc='{{ i18n "pages.xray.DirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaDomain"}}' desc='{{ i18n "pages.xray.DirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaIp"}}' desc='{{ i18n "pages.xray.DirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaDomain"}}' desc='{{ i18n "pages.xray.DirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectVNIp"}}' desc='{{ i18n "pages.xray.DirectVNIpDesc"}}' v-model="VNIpDirectSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectVNDomain"}}' desc='{{ i18n "pages.xray.DirectVNDomainDesc"}}' v-model="VNDomainDirectSettings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
@@ -196,8 +210,8 @@
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.xray.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.xray.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
<a-row :xs="24" :sm="24" :lg="12">
@@ -208,10 +222,13 @@
</template>
</a-alert>
</a-row>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.xray.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.xray.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.xray.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.xray.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
<template v-if="WarpExist">
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleWARP"}}' desc='{{ i18n "pages.xray.GoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
</template>
<a-button v-else style="margin: 10px 0;" @click="showWarp">WARP {{ i18n "pages.xray.rules.outbound" }}</a-button>
</a-collapse-panel>
</a-collapse>
</a-tab-pane>
@@ -324,79 +341,74 @@
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
<a-button type="primary" icon="plus" @click="addOutbound()">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
<a-button type="primary" icon="plus" @click="addReverse()">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
<a-row>
<a-col :sm="24" :md="12">
<p style="margin: 10px;">{{ i18n "pages.xray.Outbounds"}}</p>
<a-table :columns="outboundColumns" bordered
:row-key="r => r.key"
:data-source="outboundData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">
<template slot="action" slot-scope="text, outbound, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editOutbound(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteOutbound(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template slot="address" slot-scope="text, outbound, index">
<p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
</template>
<template slot="protocol" slot-scope="text, outbound, index">
<a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag>
<template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
</template>
</template>
</a-table>
</a-col>
<a-col :sm="24" :md="12" v-if="reverseData.length>0">
<p style="margin: 10px;">{{ i18n "pages.xray.outbound.reverse"}}</p>
<a-table :columns="reverseColumns" bordered
:row-key="r => r.key"
:data-source="reverseData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
<template slot="action" slot-scope="text, reverse, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editReverse(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteReverse(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-table>
</a-col>
</a-row>
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
<a-table :columns="outboundColumns" bordered
:row-key="r => r.key"
:data-source="outboundData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 5px' : 'margin-right: 1px;'">
<template slot="action" slot-scope="text, outbound, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editOutbound(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteOutbound(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template slot="address" slot-scope="text, outbound, index">
<p style="margin: 0 5px;" v-for="addr in findOutboundAddress(outbound)">[[ addr ]]</p>
</template>
<template slot="protocol" slot-scope="text, outbound, index">
<a-tag style="margin:0;" color="purple">[[ outbound.protocol ]]</a-tag>
<template v-if="[Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-tag style="margin:0;" color="blue">[[ outbound.streamSettings.network ]]</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='tls'" color="green">tls</a-tag>
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addReverse" }}</a-button>
<a-table :columns="reverseColumns" bordered
:row-key="r => r.key"
:data-source="reverseData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
<template slot="action" slot-scope="text, reverse, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editReverse(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteReverse(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
<a-radio-button value="xraySetting">{{ i18n "pages.xray.completeTemplate"}}</a-radio-button>
@@ -418,12 +430,13 @@
{{template "ruleModal"}}
{{template "outModal"}}
{{template "reverseModal"}}
{{template "warpModal"}}
<script>
const rulesColumns = [
{ title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
{ title: '{{ i18n "pages.xray.rules.source"}}', children: [
{ title: 'IP', dataIndex: "source", align: 'center', width: 20, ellipsis: true },
{ title: 'port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]},
{ title: 'Port', dataIndex: 'sourcePort', align: 'center', width: 10, ellipsis: true } ]},
{ title: '{{ i18n "pages.inbounds.network"}}', children: [
{ title: 'L4', dataIndex: 'network', align: 'center', width: 10 },
{ title: 'Protocol', dataIndex: 'protocol', align: 'center', width: 10, ellipsis: true },
@@ -434,7 +447,7 @@
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
{ title: 'User email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
];
@@ -458,6 +471,7 @@
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
];
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
@@ -502,23 +516,10 @@
domainStrategy: "UseIPv4"
}
},
warpSettings: {
tag: "WARP",
protocol: "socks",
settings: {
servers: [
{
address: "127.0.0.1",
port: 40000
}
]
}
},
directSettings: {
tag: "direct",
protocol: "freedom"
},
outboundDomainStrategies: ["AsIs", "UseIP", "UseIPv4", "UseIPv6"],
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
settingsData: {
protocols: {
@@ -527,14 +528,20 @@
ips: {
local: ["geoip:private"],
cn: ["geoip:cn"],
ir: ["ext:geoip_IR.dat:ir","ext:geoip_IR.dat:arvancloud","ext:geoip_IR.dat:derakcloud","ext:geoip_IR.dat:iranserver"],
ir: ["ext:geoip_IR.dat:ir"],
ru: ["geoip:ru"],
vn: ["ext:geoip_VN.dat:vn"],
},
domains: {
ads: [
"geosite:category-ads-all",
"ext:geosite_IR.dat:category-ads-all"
],
security: [
"ext:geosite_IR.dat:malware",
"ext:geosite_IR.dat:phishing",
"ext:geosite_IR.dat:cryptominers"
],
speedtest: ["geosite:speedtest"],
openai: ["geosite:openai"],
google: ["geosite:google"],
@@ -551,15 +558,18 @@
ir: [
"regexp:.*\\.ir$",
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
"ext:geosite_IR.dat:ir" // have rules to bypass all .ir domains.
"ext:geosite_IR.dat:ir"
],
vn: [
"regexp:.*\\.vn$",
"ext:geosite_VN.dat:vn",
"ext:geosite_VN.dat:ads"
]
},
familyProtectDNS: {
"servers": [
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
"1.0.0.3",
"94.140.14.15", // https://adguard-dns.io/kb/general/dns-providers/
"94.140.15.16"
"1.0.0.3"
],
"queryStrategy": "UseIPv4"
},
@@ -658,13 +668,13 @@
}
},
syncRulesWithOutbound(tag, setting) {
const newTemplateSettings = {...this.templateSettings};
const newTemplateSettings = this.templateSettings;
const haveRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === tag);
const outboundIndex = newTemplateSettings.outbounds.findIndex((o) => o.tag === tag);
if (!haveRules && outboundIndex >= 0) {
newTemplateSettings.outbounds.splice(outboundIndex, 1);
if (!haveRules && outboundIndex > 0) {
newTemplateSettings.outbounds.splice(outboundIndex);
}
if (haveRules && outboundIndex === -1) {
if (haveRules && outboundIndex < 0) {
newTemplateSettings.outbounds.push(setting);
}
this.templateSettings = newTemplateSettings;
@@ -762,6 +772,8 @@
break;
case Protocols.DNS:
return [o.settings.address + ':' + o.settings.port];
case Protocols.Wireguard:
return o.settings.peers.map(peer => peer.endpoint);
default:
return null;
}
@@ -837,17 +849,27 @@
confirm: (reverse, rules) => {
reverseModal.loading();
if(reverse.tag.length > 0){
oldtag = this.reverseData[index].tag;
this.deleteReverse(index);
oldData = this.reverseData[index];
newTemplateSettings = this.templateSettings;
if(newTemplateSettings.reverse == undefined) newTemplateSettings.reverse = {};
if(newTemplateSettings.reverse[reverse.type+'s'] == undefined) newTemplateSettings.reverse[reverse.type+'s'] = [];
newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
oldReverseIndex = newTemplateSettings.reverse[oldData.type+'s'].findIndex(rs => rs.tag == oldData.tag);
oldRuleIndex0 = oldRules.length>0 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[0])) : -1;
oldRuleIndex1 = oldRules.length==2 ? newTemplateSettings.routing.rules.findIndex(r => JSON.stringify(r) == JSON.stringify(oldRules[1])) : -1;
if(oldData.type == reverse.type){
newTemplateSettings.reverse[oldData.type + 's'][oldReverseIndex] = { tag: reverse.tag, domain: reverse.domain };
} else {
newTemplateSettings.reverse[oldData.type+'s'].splice(oldReverseIndex,1);
// delete empty object
if(newTemplateSettings.reverse[oldData.type+'s'].length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
// add other type of reverse if it is not exist
if(!newTemplateSettings.reverse[reverse.type+'s']) newTemplateSettings.reverse[reverse.type+'s'] = [];
newTemplateSettings.reverse[reverse.type+'s'].push({ tag: reverse.tag, domain: reverse.domain });
}
this.templateSettings = newTemplateSettings;
// Adjust Rules
newRules = this.templateSettings.routing.rules.filter(r => r.outboundTag != oldtag && (r.inboundTag && !r.inboundTag.includes(oldtag)));
newRules.push(...rules)
newRules = this.templateSettings.routing.rules;
oldRuleIndex0 != -1 ? newRules[oldRuleIndex0] = rules[0] : newRules.push(rules[0]);
oldRuleIndex1 != -1 ? newRules[oldRuleIndex1] = rules[1] : newRules.push(rules[1]);
this.routingRuleSettings = JSON.stringify(newRules);
}
reverseModal.close();
@@ -862,10 +884,17 @@
realIndex = reverseTypeObj.findIndex(r => r.tag==oldData.tag && r.domain==oldData.domain);
newTemplateSettings.reverse[oldData.type+'s'].splice(realIndex,1);
// delete empty objects
if(reverseTypeObj.length == 0) Reflect.deleteProperty(newTemplateSettings.reverse, oldData.type+'s');
if(Object.keys(newTemplateSettings.reverse).length === 0) Reflect.deleteProperty(newTemplateSettings, 'reverse');
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag && (r.inboundTag && !r.inboundTag.includes(oldData.tag)));
// delete related routing rules
newRules = newTemplateSettings.routing.rules;
if(oldData.type == "bridge"){
newRules = newTemplateSettings.routing.rules.filter(r => !( r.inboundTag && r.inboundTag.length == 1 && r.inboundTag[0] == oldData.tag));
} else if(oldData.type == "portal"){
newRules = newTemplateSettings.routing.rules.filter(r => r.outboundTag != oldData.tag);
}
newTemplateSettings.routing.rules = newRules;
this.templateSettings = newTemplateSettings;
@@ -910,13 +939,16 @@
rules = this.templateSettings.routing.rules;
rules.splice(index,1);
this.routingRuleSettings = JSON.stringify(rules);
},
showWarp(){
warpModal.show();
}
},
async mounted() {
await this.getXraySetting();
await this.getXrayResult();
while (true) {
await PromiseUtil.sleep(600);
await PromiseUtil.sleep(800);
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
}
},
@@ -1084,11 +1116,10 @@
},
warpDomains: {
get: function () {
return this.templateRuleGetter({ outboundTag: "WARP", property: "domain" });
return this.templateRuleGetter({ outboundTag: "warp", property: "domain" });
},
set: function (newValue) {
this.templateRuleSetter({ outboundTag: "WARP", property: "domain", data: newValue });
this.syncRulesWithOutbound("WARP", this.warpSettings);
this.templateRuleSetter({ outboundTag: "warp", property: "domain", data: newValue });
}
},
manualBlockedIPs: {
@@ -1111,10 +1142,6 @@
get: function () { return JSON.stringify(this.ipv4Domains, null, 2); },
set: debounce(function (value) { this.ipv4Domains = JSON.parse(value); }, 1000)
},
manualWARPDomains: {
get: function () { return JSON.stringify(this.warpDomains, null, 2); },
set: debounce(function (value) { this.warpDomains = JSON.parse(value); }, 1000)
},
torrentSettings: {
get: function () {
return doAllItemsExist(this.settingsData.protocols.bittorrent, this.blockedProtocols);
@@ -1151,6 +1178,18 @@
}
},
},
SecuritySettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.security, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.security];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.security.includes(data));
}
},
},
SpeedTestSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.speedtest, this.blockedDomains);
@@ -1274,6 +1313,30 @@
}
}
},
VNIpSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.vn, this.blockedIPs);
},
set: function (newValue) {
if (newValue) {
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.vn];
} else {
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.vn.includes(data));
}
}
},
VNDomainSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.vn, this.blockedDomains);
},
set: function (newValue) {
if (newValue) {
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.vn];
} else {
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.vn.includes(data));
}
}
},
IRIpDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
@@ -1346,6 +1409,35 @@
}
}
},
VNIpDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.ips.vn, this.directIPs);
},
set: function (newValue) {
if (newValue) {
this.directIPs = [...this.directIPs, ...this.settingsData.ips.vn];
} else {
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.vn.includes(data));
}
}
},
VNDomainDirectSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.vn, this.directDomains);
},
set: function (newValue) {
if (newValue) {
this.directDomains = [...this.directDomains, ...this.settingsData.domains.vn];
} else {
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.vn.includes(data));
}
}
},
WarpExist: {
get: function() {
return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0 : false;
},
},
GoogleWARPSettings: {
get: function () {
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
@@ -1398,4 +1490,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -2,77 +2,41 @@
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "pages.xray.outbound.type" }}</td>
<td>
<a-form-item>
<a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.outbound.domain" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
<a-input v-model.trim="reverseModal.reverse.tag"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'>
<a-input v-model.trim="reverseModal.reverse.domain"></a-input>
</a-form-item>
<template v-if="reverseModal.reverse.type=='bridge'">
<tr>
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
<td>
<a-form-item>
<a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.rules.outbound" }}</td>
<td>
<a-form-item>
<a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</template>
<template v-else>
<tr>
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
<td>
<a-form-item>
<a-checkbox-group
v-model="reverseModal.rules[0].inboundTag"
:options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.rules.inbound" }}</td>
<td>
<a-form-item>
<a-checkbox-group
v-model="reverseModal.rules[1].inboundTag"
:options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item>
</td>
</tr>
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
<a-checkbox-group
v-model="reverseModal.rules[0].inboundTag"
:options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
<a-checkbox-group
v-model="reverseModal.rules[1].inboundTag"
:options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item>
</template>
</table>
</a-form>
@@ -173,4 +137,4 @@
});
</script>
{{end}}
{{end}}

View File

@@ -2,149 +2,111 @@
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td style="width: 30%;">Domain Matcher</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='Domain Matcher'>
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Source IPs
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
Source IPs <a-icon type="question-circle"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Source Port
</template>
<a-input v-model.trim="ruleModal.rule.source"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">Source Port
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
Source Port <a-icon type="question-circle"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Network</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Protocol</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item>
<span>Attributes</span>
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
<tr>
<td>IP
</template>
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
</a-form-item>
<a-form-item label='Network'>
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['','TCP','UDP','TCP,UDP']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Protocol'>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Attributes'>
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span: 24}">
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
</a-input>
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
</a-input>
</a-input-group>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
IP <a-icon type="question-circle"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Domain
</template>
<a-input v-model.trim="ruleModal.rule.ip"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
Domain <a-icon type="question-circle"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Port
</template>
<a-input v-model.trim="ruleModal.rule.domain"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
<a-icon type="question-circle"></a-icon>
User <a-icon type="question-circle"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Inbound Tags</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>Outbound Tag</td>
<td>
<a-form-item>
<a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</template>
<a-input v-model.trim="ruleModal.rule.user"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
</template>
Port <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="ruleModal.rule.port"></a-input>
</a-form-item>
<a-form-item label='Inbound Tags'>
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Outbound Tag'>
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
@@ -274,4 +236,4 @@
});
</script>
{{end}}
{{end}}

View File

@@ -22,8 +22,11 @@ var job *CheckClientIpJob
var disAllowedIps []string
var ipFiles = []string{
xray.GetIPLimitLogPath(),
xray.GetIPLimitPrevLogPath(),
xray.GetIPLimitBannedLogPath(),
xray.GetIPLimitBannedPrevLogPath(),
xray.GetAccessPersistentLogPath(),
xray.GetAccessPersistentPrevLogPath(),
}
func NewCheckClientIpJob() *CheckClientIpJob {

View File

@@ -1,6 +1,9 @@
package job
import "x-ui/web/service"
import (
"x-ui/logger"
"x-ui/web/service"
)
type CheckXrayRunningJob struct {
xrayService service.XrayService
@@ -15,11 +18,15 @@ func NewCheckXrayRunningJob() *CheckXrayRunningJob {
func (j *CheckXrayRunningJob) Run() {
if j.xrayService.IsXrayRunning() {
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,37 @@ func NewClearLogsJob() *ClearLogsJob {
// Here Run is an interface method of the Job interface
func (j *ClearLogsJob) Run() {
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), 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++ {
if err := os.Truncate(logFiles[i], 0); err != nil {
// copy to previous logs
logFilePrev, err := os.OpenFile(logFilesPrev[i], 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)
}
}

View File

@@ -1146,6 +1146,8 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" {
clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else {
clientId = oldClient.ID
}
@@ -1184,6 +1186,32 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err
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) {
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
if err != nil {
@@ -1205,6 +1233,8 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, er
if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" {
clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else {
clientId = oldClient.ID
}
@@ -1266,6 +1296,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" {
clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else {
clientId = oldClient.ID
}
@@ -1324,6 +1356,8 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" {
clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else {
clientId = oldClient.ID
}
@@ -1385,6 +1419,8 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" {
clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" {
clientId = oldClient.Email
} else {
clientId = oldClient.ID
}
@@ -1613,10 +1649,10 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
return nil
}
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) {
db := database.GetDB()
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 {
return nil, err
}
@@ -1627,7 +1663,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
logger.Error("Unable to get clients from inbound")
}
for _, client := range clients {
if client.TgID == tguname {
if client.TgID == tgId {
emails = append(emails, client.Email)
}
}

View File

@@ -38,6 +38,7 @@ var defaultValueMap = map[string]string{
"timeLocation": "Asia/Tehran",
"tgBotEnable": "false",
"tgBotToken": "",
"tgBotProxy": "",
"tgBotChatId": "",
"tgRunTime": "@daily",
"tgBotBackup": "false",
@@ -56,6 +57,8 @@ var defaultValueMap = map[string]string{
"subEncrypt": "true",
"subShowInfo": "true",
"subURI": "",
"datepicker": "gregorian",
"warp": "",
}
type SettingService struct {
@@ -244,6 +247,14 @@ func (s *SettingService) SetTgBotToken(token string) error {
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) {
return s.getString("tgBotChatId")
}
@@ -413,12 +424,23 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
return s.getBool("subShowInfo")
}
func (s *SettingService) GetPageSize() (int, error) {
return s.getInt("pageSize")
}
func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI")
}
func (s *SettingService) GetPageSize() (int, error) {
return s.getInt("pageSize")
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 {
@@ -463,6 +485,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
}
result := make(map[string]interface{})

File diff suppressed because it is too large Load Diff

View File

@@ -185,7 +185,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
return err
}
if p != nil && p.IsRunning() {
if s.IsXrayRunning() {
if !isForce && p.GetConfig().Equals(xrayConfig) {
logger.Debug("It does not need to restart xray")
return nil

View File

@@ -1,8 +1,13 @@
package service
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"x-ui/util/common"
"x-ui/xray"
)
@@ -26,3 +31,142 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
}
return nil
}
func (s *XraySettingService) GetWarpData() (string, error) {
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
return warp, nil
}
func (s *XraySettingService) GetWarpConfig() (string, error) {
var warpData map[string]string
warp, err := s.SettingService.GetWarp()
if err != nil {
return "", err
}
err = json.Unmarshal([]byte(warp), &warpData)
if err != nil {
return "", err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string, error) {
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
url := fmt.Sprintf("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"
"password" = "Password"
"login" = "Login"
"login" = "Log In"
"confirm" = "Confirm"
"cancel" = "Cancel"
"close" = "Close"
@@ -8,7 +8,7 @@
"copied" = "Copied"
"download" = "Download"
"remark" = "Remark"
"enable" = "Enable"
"enable" = "Enabled"
"protocol" = "Protocol"
"search" = "Search"
"filter" = "Filter"
@@ -26,13 +26,13 @@
"edit" = "Edit"
"delete" = "Delete"
"reset" = "Reset"
"copySuccess" = "Copied successfully"
"copySuccess" = "Copied Successful"
"sure" = "Sure"
"encryption" = "Encryption"
"transmission" = "Transmission"
"host" = "Host"
"path" = "Path"
"camouflage" = "Camouflage"
"camouflage" = "Obfuscation"
"status" = "Status"
"enabled" = "Enabled"
"disabled" = "Disabled"
@@ -40,12 +40,12 @@
"depletingSoon" = "Depleting"
"offline" = "Offline"
"online" = "Online"
"domainName" = "Domain name"
"monitor" = "Listening IP"
"domainName" = "Domain Name"
"monitor" = "Listen IP"
"certificate" = "Certificate"
"fail" = "Fail"
"success" = "Success"
"getVersion" = "Get version"
"fail" = " Failed"
"success" = " Successful"
"getVersion" = "Get Version"
"install" = "Install"
"clients" = "Clients"
"usage" = "Usage"
@@ -54,69 +54,69 @@
"security" = "Security"
[menu]
"dashboard" = "System Status"
"inbounds" = "Inbounds"
"settings" = "Panel Settings"
"xray" = "Xray Settings"
"logout" = "Logout"
"link" = "Other"
"dashboard" = "OVERVIEW"
"inbounds" = "INBOUNDS"
"settings" = "PANEL SETTINGS"
"xray" = "XRAY CONFIGS"
"logout" = "LOG OUT"
"link" = "Manage"
[pages.login]
"title" = "Login"
"loginAgain" = "The login time limit has expired. Please log in again."
"title" = "Welcome"
"loginAgain" = "Your session has expired, please log in again"
[pages.login.toasts]
"invalidFormData" = "Input data format is invalid."
"emptyUsername" = "Please enter username."
"emptyPassword" = "Please enter password."
"invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Username is required"
"emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password."
"successLogin" = "Login"
[pages.index]
"title" = "System Status"
"memory" = "Memory"
"hard" = "Hard Disk"
"title" = "Overview"
"memory" = "RAM"
"hard" = "Disk"
"xrayStatus" = "Status"
"stopXray" = "Stop"
"restartXray" = "Restart"
"xraySwitch" = "SwitchV"
"xraySwitch" = "Version"
"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"
"systemLoad" = "System Load"
"systemLoadDesc" = "system load average for the past 1, 5, and 15 minutes"
"connectionTcpCountDesc" = "Total TCP connections across all network cards."
"connectionUdpCountDesc" = "Total UDP connections across all network cards."
"connectionCount" = "Number of Connections"
"upSpeed" = "Total upload speed for all network cards."
"downSpeed" = "Total download speed for all network cards."
"totalSent" = "Total upload traffic of all network cards since system startup."
"totalReceive" = "Total download data across all network cards since system startup."
"xraySwitchVersionDialog" = "Switch Xray Version"
"xraySwitchVersionDialogDesc" = "Are you sure you want to switch the Xray version to"
"dontRefresh" = "Installation is in progress, please do not refresh this page."
"systemLoadDesc" = "System load average for the past 1, 5, and 15 minutes"
"connectionTcpCountDesc" = "Total TCP connections across the system"
"connectionUdpCountDesc" = "Total UDP connections across the system"
"connectionCount" = "Connection Stats"
"upSpeed" = "Overall upload speed across the system"
"downSpeed" = "Overall download speed across the system"
"totalSent" = "Total data sent across the system since OS startup"
"totalReceive" = "Total data received across the system since OS startup"
"xraySwitchVersionDialog" = "Change Xray Version"
"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to"
"dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs"
"config" = "Config"
"backup" = "Backup & Restore"
"backupTitle" = "Backup & Restore Database"
"backupDescription" = "Remember to backup before importing a new database."
"exportDatabase" = "Download Database"
"importDatabase" = "Upload Database"
"backupTitle" = "Database Backup & Restore"
"backupDescription" = "It is recommended to make a backup before restoring a database."
"exportDatabase" = "Back Up"
"importDatabase" = "Restore"
[pages.inbounds]
"title" = "Inbounds"
"totalDownUp" = "Total Uploads/Downloads"
"totalDownUp" = "Total Sent/Received"
"totalUsage" = "Total Usage"
"inboundCount" = "Number of Inbounds"
"inboundCount" = "Total Inbounds"
"operate" = "Menu"
"enable" = "Enable"
"enable" = "Enabled"
"remark" = "Remark"
"protocol" = "Protocol"
"port" = "Port"
"traffic" = "Traffic"
"details" = "Details"
"transportConfig" = "Transport"
"expireDate" = "Expire Date"
"expireDate" = "Duration"
"resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound"
"generalActions" = "General Actions"
@@ -124,20 +124,20 @@
"update" = "Update"
"modifyInbound" = "Modify Inbound"
"deleteInbound" = "Delete Inbound"
"deleteInboundContent" = "Confirm deletion of inbound?"
"deleteInboundContent" = "Are you sure you want to delete inbound?"
"deleteClient" = "Delete Client"
"deleteClientContent" = "Are you sure you want to delete client?"
"resetTrafficContent" = "Confirm traffic reset?"
"copyLink" = "Copy Link"
"resetTrafficContent" = "Are you sure you want to reset traffic?"
"copyLink" = "Copy URL"
"address" = "Address"
"network" = "Network"
"destinationPort" = "Destination Port"
"targetAddress" = "Target Address"
"monitorDesc" = "Leave blank by default"
"meansNoLimit" = "Means No Limit"
"monitorDesc" = "Leave blank to listen on all IPs"
"meansNoLimit" = " = Unlimited. (unit: GB)"
"totalFlow" = "Total Flow"
"leaveBlankToNeverExpire" = "Leave Blank to Never Expire"
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
"leaveBlankToNeverExpire" = "Leave blank to never expire"
"noRecommendKeepDefault" = "It is recommended to keep the default"
"certificatePath" = "File Path"
"certificateContent" = "File Content"
"publicKeyPath" = "Public Key Path"
@@ -146,47 +146,47 @@
"keyContent" = "Private Key Content"
"clickOnQRcode" = "Click on QR Code to Copy"
"client" = "Client"
"export" = "Export Links"
"export" = "Export All URLs"
"clone" = "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"
"resetAllTraffic" = "Reset All Inbounds Traffic"
"resetAllTrafficTitle" = "Reset all inbounds traffic"
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
"resetAllTrafficTitle" = "Reset All Inbounds Traffic"
"resetAllTrafficContent" = "Are you sure you want to reset the traffic of all inbounds?"
"resetInboundClientTraffics" = "Reset Clients Traffic"
"resetInboundClientTrafficTitle" = "Reset all client traffic"
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
"resetInboundClientTrafficTitle" = "Reset Clients Traffic"
"resetInboundClientTrafficContent" = "Are you sure you want to reset the traffic of this inbound's clients?"
"resetAllClientTraffics" = "Reset All Clients Traffic"
"resetAllClientTrafficTitle" = "Reset all clients traffic"
"resetAllClientTrafficContent" = "Are you sure you want to reset all traffics for all clients?"
"resetAllClientTrafficTitle" = "Reset All Clients Traffic"
"resetAllClientTrafficContent" = "Are you sure you want to reset the traffic of all clients?"
"delDepletedClients" = "Delete Depleted Clients"
"delDepletedClientsTitle" = "Delete depleted clients"
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
"delDepletedClientsTitle" = "Delete Depleted Clients"
"delDepletedClientsContent" = "Are you sure you want to delete all the depleted clients?"
"email" = "Email"
"emailDesc" = "Please provide a unique email address."
"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"
"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"
"setDefaultCert" = "Set cert from panel"
"xtlsDesc" = "Xray core needs to be 1.7.5"
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
"setDefaultCert" = "Set Cert from Panel"
"xtlsDesc" = "Xray must be v1.7.5"
"realityDesc" = "Xray must be v1.8.0+"
"telegramDesc" = "Please provide Telegram or chat ID(s) without using the '@'. (get it here @userinfobot) or (use '/id' command in the bot)"
"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients."
"info" = "Info"
"same" = "Same"
"inboundData" = "Inbound's data"
"copyToClipboard" = "Copy to clipboard"
"inboundData" = "Inbound's Data"
"copyToClipboard" = "Copy to Clipboard"
"import" = "Import"
"importInbound" = "Import an inbound"
"importInbound" = "Import an Inbound"
[pages.client]
"add" = "Add Client"
"edit" = "Edit Client"
"submitAdd" = "Add Client"
"submitEdit" = "Save changes"
"submitEdit" = "Save Changes"
"clientCount" = "Number of Clients"
"bulk" = "Add Bulk"
"method" = "Method"
@@ -194,192 +194,203 @@
"last" = "Last"
"prefix" = "Prefix"
"postfix" = "Postfix"
"delayedStart" = "Start after first use"
"expireDays" = "Expire days"
"days" = "day(s)"
"renew" = "Auto renew"
"renewDesc" = "Auto renew days after expiration. 0 = disable"
"delayedStart" = "Start on Initial Use"
"expireDays" = "Duration"
"days" = "Day(s)"
"renew" = "Auto Renew"
"renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)"
[pages.inbounds.toasts]
"obtain" = "Obtain"
[pages.inbounds.stream.general]
"requestHeader" = "Request header"
"request" = "Request"
"response" = "Response"
"name" = "Name"
"value" = "Value"
[pages.inbounds.stream.tcp]
"requestVersion" = "Request version"
"requestMethod" = "Request method"
"requestPath" = "Request path"
"responseVersion" = "Response version"
"responseStatus" = "Response status"
"responseStatusDescription" = "Response status description"
"responseHeader" = "Response header"
"version" = "Version"
"method" = "Method"
"path" = "Path"
"status" = "Status"
"statusDescription" = "Status Desc"
"requestHeader" = "Request Header"
"responseHeader" = "Response Header"
[pages.inbounds.stream.quic]
"encryption" = "Encryption"
[pages.settings]
"title" = "Settings"
"title" = "Panel Settings"
"save" = "Save"
"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes."
"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 information on the server."
"restartPanel" = "Restart Panel"
"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"
"resetDefaultConfig" = "Reset to Default Configuration"
"panelSettings" = "Panel Settings"
"securitySettings" = "Security Settings"
"TGBotSettings" = "Telegram Bot Settings"
"panelListeningIP" = "Panel Listening IP"
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
"panelListeningDomain" = "Panel Listening Domain"
"panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs"
"panelPort" = "Panel Port"
"panelPortDesc" = "The port used to display this panel"
"publicKeyPath" = "Panel Certificate Public Key File Path"
"publicKeyPathDesc" = "Fill in an absolute path starting with."
"privateKeyPath" = "Panel Certificate Private Key File Path"
"privateKeyPathDesc" = "Fill in an absolute path starting with."
"panelUrlPath" = "Panel URL Root Path"
"panelUrlPathDesc" = "Must start with '/' and end with."
"pageSize" = "Pagination size"
"pageSizeDesc" = "Define page size for inbounds table. Set 0 to disable"
"remarkModel" = "Remark Model and Seperation charachter"
"sampleRemark" = "Sample remark"
"resetDefaultConfig" = "Reset to Default"
"panelSettings" = "General"
"securitySettings" = "Authentication"
"TGBotSettings" = "Telegram Bot"
"panelListeningIP" = "Listen IP"
"panelListeningIPDesc" = "The IP address for the web panel. (leave blank to listen on all IPs)"
"panelListeningDomain" = "Listen Domain"
"panelListeningDomainDesc" = "The domain name for the web panel. (leave blank to listen on all domains and IPs)"
"panelPort" = "Listen Port"
"panelPortDesc" = "The port number for the web panel. (must be an unused port)"
"publicKeyPath" = "Public Key Path"
"publicKeyPathDesc" = "The public key file path for the web panel. (begins with /)"
"privateKeyPath" = "Private Key Path"
"privateKeyPathDesc" = "The private key file path for the web panel. (begins with /)"
"panelUrlPath" = "URI Path"
"panelUrlPathDesc" = "The URI path for the web panel. (begins with / and concludes with /)"
"pageSize" = "Pagination Size"
"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)"
"remarkModel" = "Remark Model & Separation Character"
"datepicker" = "Calendar Type"
"datepickerDescription" = "Scheduled tasks will run based on this calendar."
"sampleRemark" = "Sample Remark"
"oldUsername" = "Current Username"
"currentPassword" = "Current Password"
"newUsername" = "New Username"
"newPassword" = "New Password"
"telegramBotEnable" = "Enable Telegram bot"
"telegramBotEnableDesc" = "Connect to the features of this panel through the Telegram bot"
"telegramBotEnable" = "Enable Telegram Bot"
"telegramBotEnableDesc" = "Enables the Telegram bot."
"telegramToken" = "Telegram Token"
"telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather"
"telegramChatId" = "Telegram Admin Chat IDs"
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs."
"telegramNotifyTime" = "Telegram bot notification time"
"telegramNotifyTimeDesc" = "Use Crontab timing format."
"telegramTokenDesc" = "The Telegram bot token obtained from '@BotFather'."
"telegramProxy" = "SOCKS Proxy"
"telegramProxyDesc" = "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)"
"telegramChatId" = "Admin Chat ID"
"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"
"tgNotifyBackupDesc" = "Include database backup file with report notification."
"tgNotifyBackupDesc" = "Send a database backup file with a report."
"tgNotifyLogin" = "Login Notification"
"tgNotifyLoginDesc" = "Displays the username, IP address, and time when someone tries to log into your panel."
"sessionMaxAge" = "Session maximum age"
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
"expireTimeDiff" = "Expiration threshold for notification"
"expireTimeDiffDesc" = "Get notified about account expiration before the threshold (unit: day)"
"trafficDiff" = "Traffic threshold for notification"
"trafficDiffDesc" = "Get notified about traffic exhaustion before reaching the threshold (unit: GB)"
"tgNotifyCpu" = "CPU percentage alert threshold"
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
"timeZone" = "Time zone"
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
"tgNotifyLoginDesc" = "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel."
"sessionMaxAge" = "Session Duration"
"sessionMaxAgeDesc" = "The duration for which you can stay logged in. (unit: minute)"
"expireTimeDiff" = "Expiration Date Notification"
"expireTimeDiffDesc" = "Get notified about expiration date when reaching this threshold. (unit: day)"
"trafficDiff" = "Traffic Cap Notification"
"trafficDiffDesc" = "Get notified about traffic cap when reaching this threshold. (unit: GB)"
"tgNotifyCpu" = "CPU Load Notification"
"tgNotifyCpuDesc" = "Get notified if CPU load exceeds this threshold. (unit: %)"
"timeZone" = "Time Zone"
"timeZoneDesc" = "Scheduled tasks will run based on this time zone."
"subSettings" = "Subscription"
"subEnable" = "Enable service"
"subEnableDesc" = "Subscription feature with separate configuration"
"subListen" = "Listening IP"
"subListenDesc" = "Leave blank by default to monitor all IPs"
"subPort" = "Subscription Port"
"subPortDesc" = "Port number for serving the subscription service must be unused in server"
"subCertPath" = "Subscription Certificate Public Key File Path"
"subCertPathDesc" = "Fill in an absolute path starting with '/'"
"subKeyPath" = "Subscription Certificate Private Key File Path"
"subKeyPathDesc" = "Fill in an absolute path starting with '/'"
"subPath" = "Subscription URL Root Path"
"subPathDesc" = "Must start with '/' and end with '/'"
"subDomain" = "Listening Domain"
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
"subUpdates" = "Subscription update intervals"
"subUpdatesDesc" = "Interval hours between updates in client application"
"subEncrypt" = "Encrypt configs"
"subEncryptDesc" = "Encrypt the returned configs in subscription"
"subShowInfo" = "Show usage info"
"subShowInfoDesc" = "Show remained traffic and date after config name"
"subEnable" = "Enable Subscription Service"
"subEnableDesc" = "Enables the subscription service."
"subListen" = "Listen IP"
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
"subPort" = "Listen Port"
"subPortDesc" = "The port number for the subscription service. (must be an unused port)"
"subCertPath" = "Public Key Path"
"subCertPathDesc" = "The public key file path for the subscription service. (begins with /)"
"subKeyPath" = "Private Key Path"
"subKeyPathDesc" = "The private key file path for the subscription service. (begins with /)"
"subPath" = "URI Path"
"subPathDesc" = "The URI path for the subscription service. (begins with / and concludes with /)"
"subDomain" = "Listen Domain"
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
"subUpdates" = "Update Intervals"
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
"subEncrypt" = "Encode"
"subEncryptDesc" = "The returned content of subscription service will be Base64 encoded."
"subShowInfo" = "Show Usage Info"
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"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."
[pages.xray]
"title" = "Xray Settings"
"save" = "Save Settings"
"title" = "Xray Configs"
"save" = "Save"
"restart" = "Restart Xray"
"basicTemplate" = "Basic Template"
"advancedTemplate" = "Advanced Template"
"generalConfigs" = "General Configs"
"generalConfigsDesc" = "These options will provide general adjustments."
"blockConfigs" = "Blocking Configs"
"blockConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
"blockCountryConfigs" = "Block Country Configs"
"blockCountryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
"directCountryConfigs" = "Direct Country Configs"
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
"ipv4Configs" = "IPv4 Configs"
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
"warpConfigs" = "WARP Configs"
"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."
"xrayConfigTemplate" = "Xray Configuration Template"
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template."
"xrayConfigFreedomStrategy" = "Configure Strategy for Freedom Protocol"
"xrayConfigFreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
"xrayConfigRoutingStrategy" = "Configure Domains Routing Strategy"
"xrayConfigRoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
"xrayConfigTorrent" = "Ban BitTorrent Usage"
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
"xrayConfigAds" = "Block Ads"
"xrayConfigAdsDesc" = "Change the configuration template to block ads."
"xrayConfigFamily" = "Block Malware and Adult Content"
"xrayConfigFamilyDesc" = "DNS resolvers to block malware and adult content for family protection."
"xrayConfigSpeedtest" = "Block Speedtest Websites"
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites."
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
"xrayConfigIRDomain" = "Disable connection to Iran domains"
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
"xrayConfigChinaDomain" = "Disable connection to China domains"
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
"xrayConfigDirectIRIp" = "Direct connection to Iran IP ranges"
"xrayConfigDirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
"xrayConfigDirectIRDomain" = "Direct connection to Iran domains"
"xrayConfigDirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
"xrayConfigDirectChinaIp" = "Direct connection to China IP ranges"
"xrayConfigDirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
"xrayConfigDirectChinaDomain" = "Direct connection to China domains"
"xrayConfigDirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
"xrayConfigDirectRussiaIp" = "Direct connection to Russia IP ranges"
"xrayConfigDirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
"xrayConfigDirectRussiaDomain" = "Direct connection to Russia domains"
"xrayConfigDirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
"xrayConfigGoogleWARP" = "Route Google through WARP."
"xrayConfigGoogleWARPDesc" = "Add routing for Google via WARP."
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) through WARP."
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP."
"xrayConfigNetflixWARP" = "Route Netflix through WARP."
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix via WARP."
"xrayConfigSpotifyWARP" = "Route Spotify through WARP."
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify via WARP."
"xrayConfigIRWARP" = "Route Iran domains through WARP."
"xrayConfigIRWARPDesc" = "Add routing for Iran domains via WARP."
"xrayConfigInbounds" = "Configuration of Inbounds"
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients."
"xrayConfigOutbounds" = "Configuration of Outbounds"
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server."
"xrayConfigRoutings" = "Configuration of routing rules."
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server."
"completeTemplate" = "All"
"basicTemplate" = "Basics"
"advancedTemplate" = "Advanced"
"generalConfigs" = "General Strategy"
"generalConfigsDesc" = "These options will determine general strategy adjustments."
"blockConfigs" = "Protection Shield"
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
"blockCountryConfigs" = "Block Country"
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
"directCountryConfigs" = "Direct Country"
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
"ipv4Configs" = "IPv4 Routing"
"ipv4ConfigsDesc" = "These options will route traffic based on a specific destination via IPv4."
"warpConfigs" = "WARP Routing"
"warpConfigsDesc" = "These options will route traffic based on a specific destination via WARP. (follow the guide on the Panels GitHub)"
"Template" = "Advanced Xray Configuration Template"
"TemplateDesc" = "The final Xray config file will be generated based on this template."
"FreedomStrategy" = "Freedom Protocol Strategy"
"FreedomStrategyDesc" = "Set the output strategy for the network in the Freedom Protocol."
"RoutingStrategy" = "Overall Routing Strategy"
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
"Torrent" = "Block BitTorrent Protocol"
"TorrentDesc" = "Blocks BitTorrent protocol."
"PrivateIp" = "Block Connection to Private IPs"
"PrivateIpDesc" = "Blocks establishing connections to private IP ranges."
"Ads" = "Block Ads"
"AdsDesc" = "Blocks advertising websites."
"Family" = "Family Protection"
"FamilyDesc" = "Blocks adult content, and malware websites."
"Security" = "Security Shield"
"SecurityDesc" = "Blocks malware, phishing, and cryptominers websites."
"Speedtest" = "Block Speedtest"
"SpeedtestDesc" = "Blocks establishing connectins to speedtest websites."
"IRIp" = "Block Connection to Iran IPs"
"IRIpDesc" = "Blocks establishing connections to Iran IP ranges."
"IRDomain" = "Block Connection to Iran Domains"
"IRDomainDesc" = "Blocks establishing connections to Iran domains."
"ChinaIp" = "Block Connection to China IPs"
"ChinaIpDesc" = "Blocks establishing connections to China IP ranges."
"ChinaDomain" = "Block Connection to China Domains"
"ChinaDomainDesc" = "Blocks establishing connections to China domains."
"RussiaIp" = "Block Connection to Russia IPs"
"RussiaIpDesc" = "Blocks establishing connections to Russia IP ranges."
"RussiaDomain" = "Block Connection to Russia Domains"
"RussiaDomainDesc" = "Blocks establishing connections to Russia domains."
"VNIp" = "Block Connection to Vietnam IPs"
"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"
"DirectIRIpDesc" = "Directly establishes connections to Iran IP ranges."
"DirectIRDomain" = "Direct Connection to Iran Domains"
"DirectIRDomainDesc" = "Directly establishes connections to Iran domains."
"DirectChinaIp" = "Direct Connection to China IPs"
"DirectChinaIpDesc" = "Directly establishes connections to China IP ranges."
"DirectChinaDomain" = "Direct Connection to China Domains"
"DirectChinaDomainDesc" = "Directly establishes connections to China domains."
"DirectRussiaIp" = "Direct Connection to Russia IPs"
"DirectRussiaIpDesc" = "Directly establishes connections to Russia IP ranges."
"DirectRussiaDomain" = "Direct Connection to Russia Domains"
"DirectRussiaDomainDesc" = "Directly establishes connections to Russia domains."
"DirectVNIp" = "Direct Connection to Vietnam IPs"
"DirectVNIpDesc" = "Directly establishes connections to Vietnam IP ranges."
"DirectVNDomain" = "Direct Connection to Vietnam Domains"
"DirectVNDomainDesc" = "Directly establishes connections to Vietnam domains."
"GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "Routes traffic to Google via IPv4."
"NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "Routes traffic to Netflix via IPv4."
"GoogleWARP" = "Google"
"GoogleWARPDesc" = "Add routing for Google via WARP."
"OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
"NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
"IRWARP" = "Iran domains"
"IRWARPDesc" = "Routes traffic to Iran domains via WARP."
"Inbounds" = "Inbounds"
"InboundsDesc" = "Accepting the specific clients."
"Outbounds" = "Outbounds"
"Routings" = "Routing rules"
"OutboundsDesc" = "Set the outgoing traffic pathway."
"Routings" = "Routing Rules"
"RoutingsDesc" = "The priority of each rule is important!"
"completeTemplate" = "All"
[pages.xray.rules]
"first" = "First"
@@ -393,15 +404,15 @@
"info" = "Info"
"add" = "Add Rule"
"edit" = "Edit Rule"
"useComma" = "Comma separated items"
"useComma" = "Comma-separated items"
[pages.xray.outbound]
"addOutbound" = "Add outbound"
"addReverse" = "Add reverse"
"editOutbound" = "Edit outbound"
"editReverse" = "Edit reverse"
"addOutbound" = "Add Outbound"
"addReverse" = "Add Reverse"
"editOutbound" = "Edit Outbound"
"editReverse" = "Edit Reverse"
"tag" = "Tag"
"tagDesc" = "Unique tag"
"tagDesc" = "Unique Tag"
"address" = "Address"
"reverse" = "Reverse"
"domain" = "Domain"
@@ -410,20 +421,28 @@
"portal" = "Portal"
"intercon" = "Interconnection"
[pages.xray.wireguard]
"secretKey" = "Secret Key"
"publicKey" = "Public Key"
"allowedIPs" = "Allowed IPs"
"endpoint" = "Endpoint"
"psk" = "PreShared Key"
"domainStrategy" = "Domain Strategy"
[pages.settings.security]
"admin" = "Admin"
"secret" = "Secret Token"
"loginSecurity" = "Login security"
"loginSecurityDesc" = "Enable additional user login security step"
"loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
"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]
"modifySettings" = "Modify Settings "
"getSettings" = "Get Settings "
"modifyUser" = "Modify User "
"originalUserPassIncorrect" = "Incorrect original username or password"
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
"modifySettings" = "Modify Settings"
"getSettings" = "Get Settings"
"modifyUser" = "Modify Admin"
"originalUserPassIncorrect" = "The Current username or password is invalid"
"userPassMustBeNotEmpty" = "The new username and password is empty"
[tgbot]
"keyboardClosed" = "❌ Custom keyboard closed!"
@@ -433,6 +452,7 @@
"noIpRecord" = "❗ No IP Record!"
"noInbounds" = "❗ No inbound found!"
"unlimited" = "♾ Unlimited"
"add" = "Add"
"month" = "Month"
"months" = "Months"
"day" = "Day"
@@ -441,48 +461,52 @@
"unknown" = "Unknown"
"inbounds" = "Inbounds"
"clients" = "Clients"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
[tgbot.commands]
"unknown" = "❗ Unknown command"
"unknown" = "❗ Unknown command."
"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"
"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!"
"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>"
"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."
"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, use the following command:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 The CPU usage {{ .Percent }}% is more than threshold {{ .Threshold }}%"
"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"
"selectUserFailed" = "❌ Error in user selection!"
"userSaved" = "✅ Telegram User saved."
"loginSuccess" = "✅ Successfully logged-in to the panel.\r\n"
"loginFailed" = "❗️ Login to the panel failed.\r\n"
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
"loginFailed" = "❗️ Log in to the panel failed.\r\n"
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Hostname: {{ .Hostname }}\r\n"
"version" = "🚀 X-UI Version: {{ .Version }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 3X-UI Version: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Server Memory: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TcpCount: {{ .Count }}\r\n"
"udpCount" = "🔸 UdpCount: {{ .Count }}\r\n"
"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\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"
"time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"
"expire" = "📅 Expire Date: {{ .Time }}\r\n"
"expireIn" = "📅 Expire In: {{ .Time }}\r\n"
"active" = "💡 Active: ✅ Yes\r\n"
"inactive" = "💡 Active: ❌ No\r\n"
"active" = "💡 Active: {{ .Enable }}\r\n"
"enabled" = "🚨 Enabled: {{ .Enable }}\r\n"
"online" = "🌐 Connection status: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n"
"download" = "🔽 Download: ↓{{ .Download }}\r\n"
@@ -490,10 +514,14 @@
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
"onlinesCount" = "🌐 Online Clients: {{ .Count }}\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"
"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]
"closeKeyboard" = "❌ Close Keyboard"
@@ -503,44 +531,48 @@
"confirmResetTraffic" = "✅ Confirm Reset Traffic?"
"confirmClearIps" = "✅ Confirm Clear IPs?"
"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?"
"confirmToggle" = "✅ Confirm Enable/Disable User?"
"dbBackup" = "Get DB Backup"
"serverUsage" = "Server Usage"
"getInbounds" = "Get Inbounds"
"depleteSoon" = "Deplete soon"
"depleteSoon" = "Deplete Soon"
"clientUsage" = "Get Usage"
"onlines" = "Online Clients"
"commands" = "Commands"
"refresh" = "🔄 Refresh"
"clearIPs" = "❌ Clear IPs"
"removeTGUser" = "❌ Remove Telegram User"
"selectTGUser" = "👤 Select Telegram User"
"selectOneTGUser" = "👤 Select a telegram user:"
"selectOneTGUser" = "👤 Select a Telegram User:"
"resetTraffic" = "📈 Reset Traffic"
"resetExpire" = "📅 Reset Expire Days"
"resetExpire" = "📅 Change Expiry Date"
"ipLog" = "🔢 IP Log"
"ipLimit" = "🔢 IP Limit"
"setTGUser" = "👤 Set Telegram User"
"toggle" = "🔘 Enable / Disable"
"custom" = "🔢 Custom"
"confirmNumber" = "✅ Confirm : {{ .Num }}"
"confirmNumber" = "✅ Confirm: {{ .Num }}"
"confirmNumberAdd" = "✅ Confirm adding: {{ .Num }}"
"limitTraffic" = "🚧 Traffic Limit"
"getBanLogs" = "Get Ban Logs"
[tgbot.answers]
"successfulOperation" = "✅ Successful!"
"errorOperation" = "❗ Error in Operation."
"getInboundsFailed" = "❌ Failed to get inbounds"
"canceled" = "❌ {{ .Email }} : Operation canceled."
"clientRefreshSuccess" = "✅ {{ .Email }} : Client refreshed successfully."
"IpRefreshSuccess" = "✅ {{ .Email }} : IPs refreshed successfully."
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Client's Telegram User refreshed successfully."
"resetTrafficSuccess" = "✅ {{ .Email }} : Traffic reset successfully."
"setTrafficLimitSuccess" = "✅ {{ .Email }} : Traffic limit saved successfully."
"expireResetSuccess" = "✅ {{ .Email }} : Expire days reset successfully."
"resetIpSuccess" = "✅ {{ .Email }} : IP limit {{ .Count }} saved successfully."
"clearIpSuccess" = "✅ {{ .Email }} : IPs cleared successfully."
"getIpLog" = "✅ {{ .Email }} : Get IP Log."
"getUserInfo" = "✅ {{ .Email }} : Get Telegram User Info."
"removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully."
"enableSuccess" = "✅ {{ .Email }} : Enabled 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>"
"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>"
"successfulOperation" = "✅ Operation successful!"
"errorOperation" = "❗ Error in operation."
"getInboundsFailed" = "❌ Failed to get inbounds."
"canceled" = "❌ {{ .Email }}: Operation canceled."
"clientRefreshSuccess" = "✅ {{ .Email }}: Client refreshed successfully."
"IpRefreshSuccess" = "✅ {{ .Email }}: IPs refreshed successfully."
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Client's Telegram User refreshed successfully."
"resetTrafficSuccess" = "✅ {{ .Email }}: Traffic reset successfully."
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Traffic limit saved successfully."
"expireResetSuccess" = "✅ {{ .Email }}: Expire days reset successfully."
"resetIpSuccess" = "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully."
"clearIpSuccess" = "✅ {{ .Email }}: IPs cleared successfully."
"getIpLog" = "✅ {{ .Email }}: Get IP Log."
"getUserInfo" = "✅ {{ .Email }}: Get Telegram User Info."
"removedTGUserSuccess" = "✅ {{ .Email }}: Telegram User removed successfully."
"enableSuccess" = "✅ {{ .Email }}: Enabled successfully."
"disableSuccess" = "✅ {{ .Email }}: Disabled successfully."
"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>"

View File

@@ -62,7 +62,7 @@
"link" = "Otro"
[pages.login]
"title" = "Iniciar Sesión"
"title" = "Grata"
"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente."
[pages.login.toasts]
@@ -76,10 +76,10 @@
"title" = "Estado del Sistema"
"memory" = "Memoria"
"hard" = "Disco Duro"
"xrayStatus" = "Estado de Xray"
"stopXray" = "Detener Xray"
"xrayStatus" = "Estado de"
"stopXray" = "Detener"
"restartXray" = "Reiniciar"
"xraySwitch" = "Cambiar Versión"
"xraySwitch" = "Versión"
"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."
"operationHours" = "Tiempo de Funcionamiento"
@@ -134,7 +134,7 @@
"destinationPort" = "Puerto de Destino"
"targetAddress" = "Dirección de Destino"
"monitorDesc" = "Dejar en blanco por defecto"
"meansNoLimit" = "Significa Sin Límite"
"meansNoLimit" = " = illimitata. (unidad: GB)"
"totalFlow" = "Flujo Total"
"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar"
"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada"
@@ -173,7 +173,7 @@
"setDefaultCert" = "Establecer certificado desde el panel"
"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."
"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."
"info" = "Info"
"same" = "misma"
@@ -195,27 +195,28 @@
"prefix" = "Prefijo"
"postfix" = "Sufijo"
"delayedStart" = "Iniciar después del primer uso"
"expireDays" = "Días de Expiración"
"expireDays" = "Duratio"
"days" = "día(s)"
"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]
"obtain" = "Recibir"
[pages.inbounds.stream.general]
"requestHeader" = "Encabezado de la Petición"
"request" = "Pedido"
"response" = "Respuesta"
"name" = "Nombre"
"value" = "Valor"
[pages.inbounds.stream.tcp]
"requestVersion" = "Versión de la Petición"
"requestMethod" = "Método de la Petición"
"requestPath" = "Ruta de la Petición"
"responseVersion" = "Versión de la Respuesta"
"responseStatus" = "Estado de la Respuesta"
"responseStatusDescription" = "Descripción del Estado de la Respuesta"
"responseHeader" = "Encabezado de la Respuesta"
"version" = "Versión"
"method" = "Método"
"path" = "Camino"
"status" = "Estado"
"statusDescription" = "Descripción de la Situación"
"requestHeader" = "Encabezado de solicitud"
"responseHeader" = "Encabezado de respuesta"
[pages.inbounds.stream.quic]
"encryption" = "Cifrado"
@@ -246,6 +247,8 @@
"pageSize" = "Tamaño de paginación"
"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"
"datepicker" = "selector de fechas"
"datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento"
"sampleRemark" = "Observación de muestra"
"oldUsername" = "Nombre de Usuario Actual"
"currentPassword" = "Contraseña Actual"
@@ -255,6 +258,8 @@
"telegramBotEnableDesc" = "Conéctese a las funciones de este panel a través del bot de Telegram."
"telegramToken" = "Token de Telegram"
"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"
"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"
@@ -315,71 +320,77 @@
"ipv4ConfigsDesc" = "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4."
"warpConfigs" = "Configuraciones de WARP"
"warpConfigsDesc" = "Precaución: Antes de usar estas opciones, instale WARP en modo de proxy socks5 en su servidor siguiendo los pasos en el GitHub del panel. WARP enrutará el tráfico a los sitios web a través de los servidores de Cloudflare."
"xrayConfigTemplate" = "Plantilla de Configuración de Xray"
"xrayConfigTemplateDesc" = "Genera el archivo de configuración final de Xray basado en esta plantilla."
"xrayConfigFreedomStrategy" = "Configurar Estrategia para el Protocolo Freedom"
"xrayConfigFreedomStrategyDesc" = "Establece la estrategia de salida de la red en el Protocolo Freedom."
"xrayConfigRoutingStrategy" = "Configurar Estrategia de Enrutamiento de Dominios"
"xrayConfigRoutingStrategyDesc" = "Establece la estrategia general de enrutamiento para la resolución de DNS."
"xrayConfigTorrent" = "Prohibir Uso de BitTorrent"
"xrayConfigTorrentDesc" = "Cambia la plantilla de configuración para evitar el uso de BitTorrent por parte de los usuarios."
"xrayConfigPrivateIp" = "Prohibir Conexiones a Rangos de IP Privadas"
"xrayConfigPrivateIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP privadas."
"xrayConfigAds" = "Bloquear Anuncios"
"xrayConfigAdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios."
"xrayConfigFamily" = "Bloquear Malware y Contenido para Adultos"
"xrayConfigFamilyDesc" = "Resolvedores de DNS para bloquear malware y contenido para adultos para protección familiar."
"xrayConfigSpeedtest" = "Bloquear Sitios Web de Pruebas de Velocidad"
"xrayConfigSpeedtestDesc" = "Cambia la plantilla de configuración para evitar la conexión a sitios web de pruebas de velocidad."
"xrayConfigIRIp" = "Desactivar Conexión a Rangos de IP de Irán"
"xrayConfigIRIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de Irán."
"xrayConfigIRDomain" = "Desactivar Conexión a Dominios de Irán"
"xrayConfigIRDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de Irán."
"xrayConfigChinaIp" = "Desactivar Conexión a Rangos de IP de China"
"xrayConfigChinaIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de China."
"xrayConfigChinaDomain" = "Desactivar Conexión a Dominios de China"
"xrayConfigChinaDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de China."
"xrayConfigRussiaIp" = "Desactivar Conexión a Rangos de IP de Rusia"
"xrayConfigRussiaIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de Rusia."
"xrayConfigRussiaDomain" = "Desactivar Conexión a Dominios de Rusia"
"xrayConfigRussiaDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de Rusia."
"xrayConfigDirectIRIp" = "Conexión Directa a Rangos de IP de Irán"
"xrayConfigDirectIRIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de Irán."
"xrayConfigDirectIRDomain" = "Conexión Directa a Dominios de Irán"
"xrayConfigDirectIRDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de Irán."
"xrayConfigDirectChinaIp" = "Conexión Directa a Rangos de IP de China"
"xrayConfigDirectChinaIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de China."
"xrayConfigDirectChinaDomain" = "Conexión Directa a Dominios de China"
"xrayConfigDirectChinaDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de China."
"xrayConfigDirectRussiaIp" = "Conexión Directa a Rangos de IP de Rusia"
"xrayConfigDirectRussiaIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de Rusia."
"xrayConfigDirectRussiaDomain" = "Conexión Directa a Dominios de Rusia"
"xrayConfigDirectRussiaDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de Rusia."
"xrayConfigGoogleIPv4" = "Usar IPv4 para Google"
"xrayConfigGoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4."
"xrayConfigNetflixIPv4" = "Usar IPv4 para Netflix"
"xrayConfigNetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4."
"xrayConfigGoogleWARP" = "Rutear Google a través de WARP."
"xrayConfigGoogleWARPDesc" = "Agregar enrutamiento para Google a través de WARP."
"xrayConfigOpenAIWARP" = "Rutear OpenAI (ChatGPT) a través de WARP."
"xrayConfigOpenAIWARPDesc" = "Agregar enrutamiento para OpenAI (ChatGPT) a tras de WARP."
"xrayConfigNetflixWARP" = "Rutear Netflix a través de WARP."
"xrayConfigNetflixWARPDesc" = "Agregar enrutamiento para Netflix a través de WARP."
"xrayConfigSpotifyWARP" = "Rutear Spotify a través de WARP."
"xrayConfigSpotifyWARPDesc" = "Agregar enrutamiento para Spotify a través de WARP."
"xrayConfigIRWARP" = "Rutear dominios de Irán a través de WARP."
"xrayConfigIRWARPDesc" = "Agregar enrutamiento para dominios de Irán a través de WARP."
"xrayConfigInbounds" = "Configuración de Entradas"
"xrayConfigInboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"xrayConfigOutbounds" = "Configuración de Salidas"
"xrayConfigOutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"xrayConfigRoutings" = "Configuración de Reglas de Enrutamiento"
"xrayConfigRoutingsDesc" = "Cambia la plantilla de configuración para definir reglas de enrutamiento para este servidor."
"completeTemplate" = "Todos"
"Template" = "Plantilla de Configuración de Xray"
"TemplateDesc" = "Genera el archivo de configuración final de Xray basado en esta plantilla."
"FreedomStrategy" = "Configurar Estrategia para el Protocolo Freedom"
"FreedomStrategyDesc" = "Establece la estrategia de salida de la red en el Protocolo Freedom."
"RoutingStrategy" = "Configurar Estrategia de Enrutamiento de Dominios"
"RoutingStrategyDesc" = "Establece la estrategia general de enrutamiento para la resolución de DNS."
"Torrent" = "Prohibir Uso de BitTorrent"
"TorrentDesc" = "Cambia la plantilla de configuración para evitar el uso de BitTorrent por parte de los usuarios."
"PrivateIp" = "Prohibir Conexiones 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"
"AdsDesc" = "Cambia la plantilla de configuración para bloquear anuncios."
"Family" = "Bloquee malware y contenido para adultos"
"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"
"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"
"IRIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de Irán."
"IRDomain" = "Desactivar Conexión a Dominios de Irán"
"IRDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de Irán."
"ChinaIp" = "Desactivar Conexión a Rangos de IP de China"
"ChinaIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de China."
"ChinaDomain" = "Desactivar Conexión a Dominios de China"
"ChinaDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de China."
"RussiaIp" = "Desactivar Conexión a Rangos de IP de Rusia"
"RussiaIpDesc" = "Cambia la plantilla de configuración para evitar la conexión a rangos de IP de Rusia."
"RussiaDomain" = "Desactivar Conexión a Dominios de Rusia"
"RussiaDomainDesc" = "Cambia la plantilla de configuración para evitar la conexión a dominios de Rusia."
"VNIp" = "Deshabilitar la conexión a las IP de Vietnam"
"VNIpDesc" = "Cambie la plantilla de configuración para evitar conectarse a rangos de IP de Vietnam."
"VNDomain" = "Deshabilitar la conexión a dominios de Vietnam"
"VNDomainDesc" = "Cambie la plantilla de configuración para evitar conectarse a dominios de Vietnam."
"DirectIRIp" = "Conexión Directa a Rangos de IP de Irán"
"DirectIRIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de Irán."
"DirectIRDomain" = "Conexión Directa a Dominios de Irán"
"DirectIRDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de Irán."
"DirectChinaIp" = "Conexión Directa a Rangos de IP de China"
"DirectChinaIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de China."
"DirectChinaDomain" = "Conexión Directa a Dominios de China"
"DirectChinaDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de China."
"DirectRussiaIp" = "Conexión Directa a Rangos de IP de Rusia"
"DirectRussiaIpDesc" = "Cambia la plantilla de configuración para conectarse directamente a rangos de IP de Rusia."
"DirectRussiaDomain" = "Conexión Directa a Dominios de Rusia"
"DirectRussiaDomainDesc" = "Cambia la plantilla de configuración para conectarse directamente a dominios de Rusia."
"DirectVNIp" = "Conexión directa a IP de Vietnam"
"DirectVNIpDesc" = "Cambie la plantilla de configuración para la conexión directa a rangos de IP de Vietnam."
"DirectVNDomain" = "Conexión directa a dominios de Vietnam"
"DirectVNDomainDesc" = "Cambie la plantilla de configuración para la conexión directa a dominios de Vietnam."
"GoogleIPv4" = "Usar IPv4 para Google"
"GoogleIPv4Desc" = "Agregar enrutamiento para que Google se conecte con IPv4."
"NetflixIPv4" = "Usar IPv4 para Netflix"
"NetflixIPv4Desc" = "Agregar enrutamiento para que Netflix se conecte con IPv4."
"GoogleWARP" = "Rutear Google a través de WARP."
"GoogleWARPDesc" = "Agregar enrutamiento para Google a través de WARP."
"OpenAIWARP" = "Rutear OpenAI (ChatGPT) a través de WARP."
"OpenAIWARPDesc" = "Agregar enrutamiento para OpenAI (ChatGPT) a través de WARP."
"NetflixWARP" = "Rutear Netflix a través de WARP."
"NetflixWARPDesc" = "Agregar enrutamiento para Netflix a través de WARP."
"SpotifyWARP" = "Rutear Spotify a través de WARP."
"SpotifyWARPDesc" = "Agregar enrutamiento para Spotify 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."
"Inbounds" = "Entrante"
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"Outbounds" = "Salidas"
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"Routings" = "Reglas de enrutamiento"
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
"completeTemplate" = "Todos"
[pages.xray.rules]
"first" = "Primero"
@@ -410,6 +421,14 @@
"portal" = "portal"
"intercon" = "Interconexión"
[pages.xray.wireguard]
"secretKey" = "Llave secreta"
"publicKey" = "Llave pública"
"allowedIPs" = "IP permitidas"
"endpoint" = "Punto final"
"psk" = "Clave precompartida"
"domainStrategy" = "Estrategia de dominio"
[pages.settings.security]
"admin" = "Administrador"
"secret" = "Token Secreto"
@@ -433,6 +452,7 @@
"noIpRecord" = "❗ ¡Sin Registro de IP!"
"noInbounds" = "❗ ¡No se encontraron entradas!"
"unlimited" = "♾ Ilimitado"
"add" = "Agregar"
"month" = "Mes"
"months" = "Meses"
"day" = "Día"
@@ -441,6 +461,8 @@
"unknown" = "Desconocido"
"inbounds" = "Entradas"
"clients" = "Clientes"
"offline" = "🔴 Sin conexión"
"online" = "🟢 En línea"
[tgbot.commands]
"unknown" = "❗ Comando desconocido"
@@ -451,8 +473,8 @@
"status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"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>"
"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."
"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>"
[tgbot.messages]
"cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%"
@@ -467,7 +489,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\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"
"serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n"
@@ -481,19 +503,23 @@
"port" = "🔌 Puerto: {{ .Port }}\r\n"
"expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n"
"expireIn" = "📅 Vence en: {{ .Time }}\r\n"
"active" = "💡 Activo: ✅ Sí\r\n"
"inactive" = "💡 Activo: ❌ No\r\n"
"active" = "💡 Activo: {{ .Enable }}\r\n"
"enabled" = "🚨 Habilitado: {{ .Enable }}\r\n"
"online" = "🌐 Estado de conexión: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Subida: ↑{{ .Upload }}\r\n"
"download" = "🔽 Bajada: ↓{{ .Download }}\r\n"
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Agotado {{ .Type }}: \r\n"
"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}: \r\n"
"exhaustedMsg" = "🚨 Agotado {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Clientes en línea: {{ .Count }}\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"
"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]
"closeKeyboard" = "❌ Cerrar Teclado"
@@ -503,11 +529,13 @@
"confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?"
"confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?"
"confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?"
"confirmToggle" = " ✅ ¿Confirmar habilitar/deshabilitar usuario?"
"dbBackup" = "Obtener Copia de Seguridad de BD"
"serverUsage" = "Uso del Servidor"
"getInbounds" = "Obtener Entradas"
"depleteSoon" = "Pronto se Agotará"
"clientUsage" = "Obtener Uso"
"onlines" = "Clientes en línea"
"commands" = "Comandos"
"refresh" = "🔄 Actualizar"
"clearIPs" = "❌ Limpiar IPs"
@@ -515,14 +543,16 @@
"selectTGUser" = "👤 Seleccionar Usuario de Telegram"
"selectOneTGUser" = "👤 Selecciona un usuario de telegram:"
"resetTraffic" = "📈 Reiniciar Tráfico"
"resetExpire" = "📅 Reiniciar Días de Vencimiento"
"resetExpire" = "📅 Cambiar fecha de Vencimiento"
"ipLog" = "🔢 Registro de IP"
"ipLimit" = "🔢 Límite de IP"
"setTGUser" = "👤 Establecer Usuario de Telegram"
"toggle" = "🔘 Habilitar / Deshabilitar"
"custom" = "🔢 Costumbre"
"confirmNumber" = "✅ Confirmar : {{ .Num }}"
"confirmNumber" = "✅ Confirmar: {{ .Num }}"
"confirmNumberAdd" = "✅ Confirmar agregando: {{ .Num }}"
"limitTraffic" = "🚧 Límite de tráfico"
"getBanLogs" = "Registros de prohibición"
[tgbot.answers]
"successfulOperation" = "✅ ¡Exitosa!"
@@ -542,5 +572,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente."
"enableSuccess" = "✅ {{ .Email }} : Habilitado 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>"
"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>"
"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>"

View File

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

View File

@@ -57,12 +57,12 @@
"dashboard" = "Статус системы"
"inbounds" = "Подключения"
"settings" = "Настройки панели"
"xray" = "Xray Настройки"
"xray" = "Настройки Xray"
"logout" = "Выход"
"link" = "Прочее"
"link" = "менеджмент"
[pages.login]
"title" = "Логин"
"title" = "Добро пожаловать"
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова"
[pages.login.toasts]
@@ -77,9 +77,9 @@
"memory" = "Память"
"hard" = "Жесткий диск"
"xrayStatus" = "Статус"
"stopXray" = "Остановить Xray"
"restartXray" = "Рестарт Xray"
"xraySwitch" = "Переключить версию"
"stopXray" = "Остановить"
"restartXray" = "Перезапустить"
"xraySwitch" = "Версия"
"xraySwitchClick" = "Выберите желаемую версию"
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
"operationHours" = "Время работы системы"
@@ -134,7 +134,7 @@
"destinationPort" = "Порт назначения"
"targetAddress" = "Целевой адрес"
"monitorDesc" = "Оставьте пустым по умолчанию"
"meansNoLimit" = "Значит без ограничений"
"meansNoLimit" = " = Без ограничений (значение: ГБ)"
"totalFlow" = "Общий расход"
"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы не истекало"
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
@@ -173,7 +173,7 @@
"setDefaultCert" = "Установить сертификат с панели"
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
"realityDesc" = "Версия Xray должна быть не ниже 1.8.0"
"telegramDesc" = "Используйте идентификатор Telegram без символа @ или идентификатора чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"telegramDesc" = "Используйте только ID чата (можно получить его здесь @userinfobot или использовать команду '/id' в боте)"
"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее', также вы можете использовать одно и то же имя для нескольких конфигураций"
"info" = "Информация"
"same" = "Тот же"
@@ -195,26 +195,27 @@
"prefix" = "Префикс"
"postfix" = "Постфикс"
"delayedStart" = "Начать с момента первого подключения"
"expireDays" = "Срок действия"
"expireDays" = "Длительность"
"days" = "дней"
"renew" = "Автопродление"
"renewDesc" = "Автоматическое продление через несколько дней после истечения срока действия. 0 = отключить"
"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)"
[pages.inbounds.toasts]
"obtain" = "Получить"
[pages.inbounds.stream.general]
"requestHeader" = "Заголовок запроса"
"request" = "Запрос"
"response" = "Ответ"
"name" = "Имя"
"value" = "Значение"
"value" = "Ценить"
[pages.inbounds.stream.tcp]
"requestVersion" = "Версия запроса"
"requestMethod" = "Метод запроса"
"requestPath" = "Путь запроса"
"responseVersion" = "Версия ответа"
"responseStatus" = "Статус ответа"
"responseStatusDescription" = "Описание статуса ответа"
"version" = "Версия"
"method" = "Метод"
"path" = "Путь"
"status" = "Положение дел"
"statusDescription" = "Описание статуса"
"requestHeader" = "Заголовок запроса"
"responseHeader" = "Заголовок ответа"
[pages.inbounds.stream.quic]
@@ -246,6 +247,8 @@
"pageSize" = "Размер нумерации страниц"
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
"remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "выбор даты"
"datepickerDescription" = "Тип календаря выбора указывает дату истечения срока действия."
"sampleRemark" = "Пример замечания"
"oldUsername" = "Текущее имя пользователя"
"currentPassword" = "Текущий пароль"
@@ -255,6 +258,8 @@
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота"
"telegramToken" = "Токен Telegram бота"
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
"telegramProxy" = "Прокси Socks5"
"telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5. Настройте его параметры согласно руководству."
"telegramChatId" = "Telegram ID админа бота"
"telegramChatIdDesc" = "Множественные идентификаторы чата, разделенные запятыми. Чтобы получить свои идентификаторы чатов, используйте @userinfobot или команду '/id' в боте."
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
@@ -268,7 +273,7 @@
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
"trafficDiff" = "Порог трафика для уведомления"
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: Гб)"
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления"
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение: %)"
"timeZone" = "Часовой пояс"
@@ -298,9 +303,9 @@
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
[pages.xray]
"title" = "Xray Настройки"
"title" = "Настройки Xray"
"save" = "Сохранить настройки"
"restart" = "Перезапустить рентген"
"restart" = "Перезапустить Xray"
"basicTemplate" = "Базовый шаблон"
"advancedTemplate" = "Расширенный шаблон"
"generalConfigs" = "Основные настройки"
@@ -315,71 +320,77 @@
"ipv4ConfigsDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
"warpConfigs" = "Настройки WARP"
"warpConfigsDesc" = "Внимание: перед использованием этих параметров установите WARP в режиме прокси-сервера socks5 на свой сервер, следуя инструкциям на GitHub панели. WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона"
"xrayConfigFreedomStrategy" = "Настройка стратегии протокола Freedom"
"xrayConfigFreedomStrategyDesc" = "Установка стратегию вывода сети в протоколе Freedom"
"xrayConfigRoutingStrategy" = "Настройка стратегии маршрутизации доменов"
"xrayConfigRoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS"
"xrayConfigTorrent" = "Запрет использования BitTorrent"
"xrayConfigTorrentDesc" = "Изменение шаблона конфигурации для предупреждения использования BitTorrent пользователями"
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
"xrayConfigPrivateIpDesc" = "Изменение шаблона конфигурации для предупреждения подключения к диапазонам частных IP-адресов"
"xrayConfigAds" = "Блокировка рекламы"
"xrayConfigAdsDesc" = "Изменение конфигурации для блокировки рекламы"
"xrayConfigFamily" = "Блокировать вредоносное ПО и контент для взрослых"
"xrayConfigFamilyDesc" = "Резольверы DNS для блокировки вредоносных программ и контента для взрослых для защиты семьи"
"xrayConfigSpeedtest" = "Блокировать сайты для проверки скорости"
"xrayConfigSpeedtestDesc" = "Изменение шаблона конфигурации для предупреждения подключения к веб-сайтам для тестирования скорости"
"xrayConfigIRIp" = "Заблокировать подключения к диапазонам IP-адресов Ирана"
"xrayConfigIRIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонам IP-адресов Ирана"
"xrayConfigIRDomain" = "Заблокировать подключения к доменам Ирана"
"xrayConfigIRDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам Ирана"
"xrayConfigChinaIp" = "Заблокировать подключения к диапазонам IP-адресов Китая"
"xrayConfigChinaIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонам IP-адресов Китая"
"xrayConfigChinaDomain" = "Заблокировать подключения к доменам Китая"
"xrayConfigChinaDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам Китая"
"xrayConfigRussiaIp" = "Заблокировать подключения к диапазонам IP-адресов России"
"xrayConfigRussiaIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонами IP-адресов России"
"xrayConfigRussiaDomain" = "Заблокировать подключения к доменам России"
"xrayConfigRussiaDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам России"
"xrayConfigDirectIRIp" = "Прямое подключения к диапазонам IP-адресов Ирана"
"xrayConfigDirectIRIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
"xrayConfigDirectIRDomain" = "Прямое подключение к доменам Ирана"
"xrayConfigDirectIRDomainDesc" = "Изменение шаблон конфигурации для прямого подключения к доменам Ирана"
"xrayConfigDirectChinaIp" = "Прямое подключение к диапазонам IP-адресов Китая"
"xrayConfigDirectChinaIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов Китая"
"xrayConfigDirectChinaDomain" = "Прямое подключение к доменам Китая"
"xrayConfigDirectChinaDomainDesc" = "Изменение шаблона конфигурации для прямого подключения к доменам Китая"
"xrayConfigDirectRussiaIp" = "Прямое подключение к диапазонам IP-адресов России"
"xrayConfigDirectRussiaIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов России"
"xrayConfigDirectRussiaDomain" = "Прямое подключение к доменам России"
"xrayConfigDirectRussiaDomainDesc" = "Изменение шаблона конфигурации для прямого подключения к доменам России"
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
"xrayConfigGoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
"xrayConfigNetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
"xrayConfigGoogleWARP" = "Маршрутизация Google через WARP"
"xrayConfigGoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
"xrayConfigOpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP"
"xrayConfigOpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP"
"xrayConfigNetflixWARP" = "Маршрутизация Netflix через WARP"
"xrayConfigNetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
"xrayConfigSpotifyWARP" = "Маршрутизация Spotify через WARP"
"xrayConfigSpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
"xrayConfigIRWARP" = "Маршрутизация доменов Ирана через WARP"
"xrayConfigIRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP"
"xrayConfigInbounds" = "Конфигурация подключений"
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"xrayConfigOutbounds" = "Конфигурация исходящих"
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"xrayConfigRoutings" = "Настройка правил маршрутизации"
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации для определения правил маршрутизации для этого сервера"
"completeTemplate" = "Все"
"Template" = "Шаблон конфигурации Xray"
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона"
"FreedomStrategy" = "Настройка стратегии протокола Freedom"
"FreedomStrategyDesc" = "Установка стратегию вывода сети в протоколе Freedom"
"RoutingStrategy" = "Настройка стратегии маршрутизации доменов"
"RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS"
"Torrent" = "Запрет использования BitTorrent"
"TorrentDesc" = "Изменение шаблона конфигурации для предупреждения использования BitTorrent пользователями"
"PrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
"PrivateIpDesc" = "Изменение шаблона конфигурации для предупреждения подключения к диапазонам частных IP-адресов"
"Ads" = "Блокировка рекламы"
"AdsDesc" = "Изменение конфигурации для блокировки рекламы"
"Family" = "Блокируйте вредоносное ПО и контент для взрослых"
"FamilyDesc" = "DNS-преобразователи Cloudflare для блокировки вредоносного ПО и контента для взрослых в целях защиты семьи."
"Security" = "Блокируйте вредоносное ПО, фишинговые сайты и сайты криптомайнеров"
"SecurityDesc" = "Изменение шаблона конфигурации для защиты безопасности."
"Speedtest" = "Блокировать сайты для проверки скорости"
"SpeedtestDesc" = "Изменение шаблона конфигурации для предупреждения подключения к веб-сайтам для тестирования скорости"
"IRIp" = "Заблокировать подключения к диапазонам IP-адресов Ирана"
"IRIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонам IP-адресов Ирана"
"IRDomain" = "Заблокировать подключения к доменам Ирана"
"IRDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам Ирана"
"ChinaIp" = "Заблокировать подключения к диапазонам IP-адресов Китая"
"ChinaIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонам IP-адресов Китая"
"ChinaDomain" = "Заблокировать подключения к доменам Китая"
"ChinaDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам Китая"
"RussiaIp" = "Заблокировать подключения к диапазонам IP-адресов России"
"RussiaIpDesc" = "Изменение конфигурации, чтобы заблокировать подключения к диапазонами IP-адресов России"
"RussiaDomain" = "Заблокировать подключения к доменам России"
"RussiaDomainDesc" = "Изменение конфигурации, чтобы заблокировать подключения к доменам России"
"VNIp" = "Отключить подключение к IP-адресам Вьетнама"
"VNIpDesc" = "Измените шаблон конфигурации, чтобы избежать подключения к диапазонам IP-адресов Вьетнама"
"VNDomain" = "Отключить подключение к доменам Вьетнама"
"VNDomainDesc" = "Измените шаблон конфигурации, чтобы избежать подключения к доменам Вьетнама."
"DirectIRIp" = "Прямое подключения к диапазонам IP-адресов Ирана"
"DirectIRIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
"DirectIRDomain" = "Прямое подключение к доменам Ирана"
"DirectIRDomainDesc" = "Изменение шаблон конфигурации для прямого подключения к доменам Ирана"
"DirectChinaIp" = "Прямое подключение к диапазонам IP-адресов Китая"
"DirectChinaIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов Китая"
"DirectChinaDomain" = "Прямое подключение к доменам Китая"
"DirectChinaDomainDesc" = "Изменение шаблона конфигурации для прямого подключения к доменам Китая"
"DirectRussiaIp" = "Прямое подключение к диапазонам IP-адресов России"
"DirectRussiaIpDesc" = "Изменение шаблона конфигурации для прямого подключения к диапазонам IP-адресов России"
"DirectRussiaDomain" = "Прямое подключение к доменам России"
"DirectRussiaDomainDesc" = "Изменение шаблона конфигурации для прямого подключения к доменам России"
"DirectVNIp" = "Прямое подключение к IP-адресам Вьетнама"
"DirectVNIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Вьетнама"
"DirectVNDomain" = "Прямое подключение к доменам Вьетнама"
"DirectVNDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Вьетнама"
"GoogleIPv4" = "Использовать IPv4 для Google"
"GoogleIPv4Desc" = "Добавить маршрутизацию для Google для подключения к IPv4"
"NetflixIPv4" = "Использовать IPv4 для Netflix"
"NetflixIPv4Desc" = "Добавить маршрутизацию для Netflix для подключения к IPv4"
"GoogleWARP" = "Маршрутизация Google через WARP"
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
"OpenAIWARP" = "Маршрутизация OpenAI (ChatGPT) через WARP"
"OpenAIWARPDesc" = "Добавить маршрутизацию для OpenAI (ChatGPT) через WARP"
"NetflixWARP" = "Маршрутизация Netflix через WARP"
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
"SpotifyWARP" = "Маршрутизация Spotify через WARP"
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
"IRWARP" = "Маршрутизация доменов Ирана через WARP"
"IRWARPDesc" = "Добавить маршрутизацию для доменов Ирана через WARP"
"Inbounds" = "Входящие"
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"Outbounds" = "Исходящие"
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"Routings" = "Правила маршрутизации"
"RoutingsDesc" = "Важен приоритет каждого правила!"
"completeTemplate" = "Все"
[pages.xray.rules]
"first" = "Первый"
@@ -389,7 +400,7 @@
"source" = "Источник"
"dest" = "Пункт назначения"
"inbound" = "Входящий"
"outboun" = "Исходящий"
"outbound" = "Исходящий"
"info" = "Информация"
"add" = "Добавить правило"
"edit" = "Редактировать правило"
@@ -410,6 +421,14 @@
"portal" = "Портал"
"intercon" = "Соединение"
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ"
"allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка"
"psk" = "Общий ключ"
"domainStrategy" = "Стратегия домена"
[pages.settings.security]
"admin" = "Админ"
"secret" = "Секретный токен"
@@ -433,6 +452,7 @@
"noIpRecord" = "❗ Нет записей об IP-адресе!"
"noInbounds" = "❗ Входящих соединений не найдено!"
"unlimited" = "♾ Неограниченно"
"add" = "Добавить"
"month" = "Месяц"
"months" = "Месяцев"
"day" = "День"
@@ -441,6 +461,8 @@
"unknown" = "Неизвестно"
"inbounds" = "Входящие"
"clients" = "Клиенты"
"offline" = "🔴 Офлайн"
"online" = "🟢 Онлайн"
[tgbot.commands]
"unknown" = "❗ Неизвестная команда"
@@ -451,8 +473,8 @@
"status" = "✅ Бот работает нормально!"
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</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."
"helpAdminCommands" = "Поиск по электронной почте клиента:\r\n<code>/usage [Email]</code>\r\n\r\nПоиск входящих соединений (со статистикой клиента):\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "Для получения статистики используйте следующую команду:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%"
@@ -467,7 +489,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\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"
"serverLoad" = "📈 Загрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Память сервера: {{ .Current }}/{{ .Total }}\r\n"
@@ -482,7 +504,8 @@
"expire" = "📅 Дата окончания: {{ .Time }}\r\n"
"expireIn" = "📅 Окончание через: {{ .Time }}\r\n"
"active" = "💡 Активен: ✅ Да\r\n"
"inactive" = "💡 Активен: ❌ Нет\r\n"
"enabled" = "🚨 Включен: {{ .Enable }}\r\n"
"online" = "🌐 Статус соединения: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n"
"download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n"
@@ -490,10 +513,13 @@
"TGUser" = "👤 Пользователь Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Клиентов онлайн: {{ .Count }}\r\n"
"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n \r\n"
"depleteSoon" = "🔜 Скоро исчерпание: {{ .Deplete }}\r\n\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]
"closeKeyboard" = "❌ Закрыть клавиатуру"
@@ -503,11 +529,13 @@
"confirmResetTraffic" = "✅ Подтвердить сброс трафика?"
"confirmClearIps" = "✅ Подтвердить очистку IP?"
"confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?"
"confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?"
"dbBackup" = "Получить резервную копию DB"
"serverUsage" = "Использование сервера"
"getInbounds" = "Получить входящие потоки"
"depleteSoon" = "Скоро исчерпание"
"clientUsage" = "Получить использование"
"onlines" = "Онлайн-клиенты"
"commands" = "Команды"
"refresh" = "🔄 Обновить"
"clearIPs" = "❌ Очистить IP"
@@ -515,14 +543,16 @@
"selectTGUser" = "👤 Выбрать пользователя Telegram"
"selectOneTGUser" = "👤 Выберите пользователя Telegram:"
"resetTraffic" = "📈 Сбросить трафик"
"resetExpire" = "📅 Сбросить дату окончания"
"resetExpire" = "📅 Изменить дату окончания"
"ipLog" = "🔢 Лог IP"
"ipLimit" = "🔢 Лимит IP"
"setTGUser" = "👤 Установить пользователя Telegram"
"toggle" = "🔘 Вкл./Выкл."
"custom" = "🔢 Обычай"
"confirmNumber" = "✅ Подтвердить : {{ .Num }}"
"custom" = "🔢 Свой"
"confirmNumber" = "✅ Подтвердить: {{ .Num }}"
"confirmNumberAdd" = "✅ Подтвердить добавление: {{ .Num }}"
"limitTraffic" = "🚧 Лимит трафика"
"getBanLogs" = "Логи блокировок"
[tgbot.answers]
"successfulOperation" = "✅ Успешный!"
@@ -542,5 +572,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален."
"enableSuccess" = "✅ {{ .Email }}: Включено успешно."
"disableSuccess" = "✅ {{ .Email }}: Отключено успешно."
"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>"
"askToAddUserName" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваше имя пользователя или идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаше имя пользователя: <b>@{{ .TgUserName }}</b>\r\n\r\nВаш идентификатор пользователя: <b>{{ .TgUserID }}</b>"
"askToAddUserId" = "Ваша конфигурация не найдена!\r\nПожалуйста, попросите администратора использовать ваш идентификатор пользователя Telegram в ваших конфигурациях.\r\n\r\nВаш идентификатор пользователя: <code>{{ .TgUserID }}</code>"

View File

@@ -1,6 +1,6 @@
"username" = "Tên người dùng"
"password" = "Mật khẩu"
"login" = "Đăng nhập..."
"login" = "Đăng nhập"
"confirm" = "Xác nhận"
"cancel" = "Hủy bỏ"
"close" = "Đóng"
@@ -36,10 +36,10 @@
"status" = "Trạng thái"
"enabled" = "Đã kích hoạt"
"disabled" = "Đã tắt"
"depleted" = "Đã cạn kiệt"
"depletingSoon" = "Đang cạn kiệt"
"depleted" = "Depleted"
"depletingSoon" = "Depleting..."
"offline" = "Ngoại tuyến"
"online" = "Ngoại tuyến"
"online" = "Trực tuyến"
"domainName" = "Tên miền"
"monitor" = "Listening IP"
"certificate" = "Chứng chỉ"
@@ -47,22 +47,22 @@
"success" = "Thành công"
"getVersion" = "Lấy phiên bản"
"install" = "Cài đặt"
"clients" = "Khách hàng"
"clients" = "Các khách hàng"
"usage" = "Sử dụng"
"secretToken" = "secretToken"
"secretToken" = "Mã bí mật"
"remained" = "Còn lại"
"security" = "Bảo vệ"
[menu]
"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"
"logout" = "Đăng xuất"
"xray" = "Xray Cài đặt"
"link" = "Khác"
"xray" = "Cài đặt Xray"
"link" = "sự quản lý"
[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."
[pages.login.toasts]
@@ -75,21 +75,21 @@
[pages.index]
"title" = "Trạng thái hệ thống"
"memory" = "Bộ nhớ"
"hard" = "Ổ cứng"
"xrayStatus" = "Trạng thái của Xray"
"stopXray" = "Dừng Xray"
"restartXray" = "Khởi động lại Xray"
"xraySwitch" = "Chuyển đổi phiên bản"
"hard" = "Dung lượng"
"xrayStatus" = "Trạng thái"
"stopXray" = "Dừng lại"
"restartXray" = "Khởi động lại"
"xraySwitch" = "Phiên bản"
"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."
"operationHours" = "Thời gian hoạt động"
"systemLoad" = "Tải hệ thống"
"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."
"connectionUdpCountDesc" = "Tổng số kết nối UDP 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 thẻ mạng."
"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."
"downSpeed" = "Tổng tốc độ tải xuống 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 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."
"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"
@@ -134,7 +134,7 @@
"destinationPort" = "Cổng đích"
"targetAddress" = "Địa chỉ mục tiêu"
"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"
"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"
@@ -145,11 +145,11 @@
"keyPath" = "Đường dẫn khóa riêng tư"
"keyContent" = "Nội dung khóa riêng tư"
"clickOnQRcode" = "Nhấn vào Mã QR để sao chép"
"client" = "Client"
"client" = "Máy Khách"
"export" = "Xuất liên kết"
"clone" = "Sao chép"
"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"
"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"
@@ -173,7 +173,7 @@
"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển"
"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."
"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"
"info" = "Thông tin"
"same" = "Giống nhau"
@@ -195,26 +195,27 @@
"prefix" = "Tiền tố"
"postfix" = "Hậu tố"
"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"
"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]
"obtain" = "Nhận"
[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"
"value" = "Giá trị"
[pages.inbounds.stream.tcp]
"requestVersion" = "Phiên bản yêu cầu"
"requestMethod" = "Phương thức yêu cầu"
"requestPath" = "Đường dẫn yêu cầu"
"responseVersion" = "Phiên bản phản hồi"
"responseStatus" = "Trạng thái phản hồi"
"responseStatusDescription" = "Mô tả trạng thái phản hồi"
"version" = "Phiên bản"
"method" = "Phương pháp"
"path" = "Con đường"
"status" = "Trạng thái"
"statusDescription" = "Tình trạng Mô tả"
"requestHeader" = "Tiêu đề yêu cầu"
"responseHeader" = "Tiêu đề phản hồi"
[pages.inbounds.stream.quic]
@@ -246,6 +247,8 @@
"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"
"remarkModel" = "Ghi chú mô hình và ký tự phân tách"
"datepicker" = "bảng chọn ngày"
"datepickerDescription" = "Loại lịch chọn chỉ định ngày hết hạn"
"sampleRemark" = "Nhận xét mẫu"
"oldUsername" = "Tên người dùng hiện tại"
"currentPassword" = "Mật khẩu hiện tại"
@@ -255,6 +258,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"
"telegramToken" = "Token Telegram"
"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"
"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"
@@ -298,7 +303,7 @@
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
[pages.xray]
"title" = "Xray Cài đặt"
"title" = "Cài đặt Xray"
"save" = "Lưu cài đặt"
"restart" = "Khởi động lại Xray"
"basicTemplate" = "Mẫu Cơ bản"
@@ -315,71 +320,77 @@
"ipv4ConfigsDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4."
"warpConfigs" = "Cấu hình WARP"
"warpConfigsDesc" = "Cảnh báo: Trước khi sử dụng những tùy chọn này, hãy cài đặt WARP ở chế độ proxy socks5 trên máy chủ của bạn bằng cách làm theo các bước trên GitHub của bảng điều khiển. WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
"xrayConfigTemplate" = "Mẫu Cấu hình Xray"
"xrayConfigTemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này."
"xrayConfigFreedomStrategy" = "Cấu hình Chiến lược cho Giao thức Freedom"
"xrayConfigFreedomStrategyDesc" = "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom."
"xrayConfigRoutingStrategy" = "Cấu hình Chiến lược Định tuyến Tên miền"
"xrayConfigRoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS."
"xrayConfigTorrent" = "Cấu hình sử dụng BitTorrent"
"xrayConfigTorrentDesc" = "Thay đổi mẫu cấu hình để tránh việc người dùng sử dụng BitTorrent."
"xrayConfigPrivateIp" = "Cấm kết nối đến dải IP Riêng tư"
"xrayConfigPrivateIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP riêng tư."
"xrayConfigAds" = "Chặn Quảng cáo"
"xrayConfigAdsDesc" = "Thay đổi mẫu cấu hình để chặn quảng cáo."
"xrayConfigFamily" = "Chặn Phần mềm độc hại và Nội dung cho Người lớn"
"xrayConfigFamilyDesc" = "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."
"xrayConfigSpeedtest" = "Chặn Trang web Speedtest"
"xrayConfigSpeedtestDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các trang web Speedtest."
"xrayConfigIRIp" = "Vô hiệu hóa kết nối đến dải IP của Iran"
"xrayConfigIRIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Iran."
"xrayConfigIRDomain" = "Vô hiệu hóa kết nối đến tên miền của Iran"
"xrayConfigIRDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Iran."
"xrayConfigChinaIp" = "Vô hiệu hóa kết nối đến dải IP của Trung Quốc"
"xrayConfigChinaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Trung Quốc."
"xrayConfigChinaDomain" = "Vô hiệu hóa kết nối đến tên miền của Trung Quốc"
"xrayConfigChinaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Trung Quốc."
"xrayConfigRussiaIp" = "Vô hiệu hóa kết nối đến dải IP của Nga"
"xrayConfigRussiaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Nga."
"xrayConfigRussiaDomain" = "Vô hiệu hóa kết nối đến tên miền của Nga"
"xrayConfigRussiaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Nga."
"xrayConfigDirectIRIp" = "Kết nối trực tiếp đến dải IP của Iran"
"xrayConfigDirectIRIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Iran."
"xrayConfigDirectIRDomain" = "Kết nối trực tiếp đến tên miền của Iran"
"xrayConfigDirectIRDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Iran."
"xrayConfigDirectChinaIp" = "Kết nối trực tiếp đến dải IP của Trung Quốc"
"xrayConfigDirectChinaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Trung Quốc."
"xrayConfigDirectChinaDomain" = "Kết nối trực tiếp đến tên miền của Trung Quốc"
"xrayConfigDirectChinaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Trung Quốc."
"xrayConfigDirectRussiaIp" = "Kết nối trực tiếp đến dải IP của Nga"
"xrayConfigDirectRussiaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Nga."
"xrayConfigDirectRussiaDomain" = "Kết nối trực tiếp đến tên miền của Nga"
"xrayConfigDirectRussiaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Nga."
"xrayConfigGoogleIPv4" = "Sử dụng IPv4 cho Google"
"xrayConfigGoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
"xrayConfigNetflixIPv4" = "Sử dụng IPv4 cho Netflix"
"xrayConfigNetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
"xrayConfigGoogleWARP" = "Định tuyến Google qua WARP."
"xrayConfigGoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
"xrayConfigOpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
"xrayConfigOpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
"xrayConfigNetflixWARP" = "Định tuyến Netflix qua WARP."
"xrayConfigNetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
"xrayConfigSpotifyWARP" = "Định tuyến Spotify qua WARP."
"xrayConfigSpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP."
"xrayConfigIRWARP" = "Định tuyến tên miền của Iran qua WARP."
"xrayConfigIRWARPDesc" = "Thêm định tuyến cho các tên miền của Iran qua WARP."
"xrayConfigInbounds" = "Cấu hình của Inbounds"
"xrayConfigInboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
"xrayConfigOutbounds" = "Cấu hình của Outbounds"
"xrayConfigOutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
"xrayConfigRoutings" = "Cấu hình của Luật Định tuyến."
"xrayConfigRoutingsDesc" = "Thay đổi mẫu cấu hình để xác định luật định tuyến cho máy chủ này."
"completeTemplate" = "All"
"Inbounds" = "Vào"
"Outbounds" = "Outbounds"
"Template" = "Mẫu Cấu hình Xray"
"TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này."
"FreedomStrategy" = "Cấu hình Chiến lược cho Giao thức Freedom"
"FreedomStrategyDesc" = "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom."
"RoutingStrategy" = "Cấu hình Chiến lược Định tuyến Tên miền"
"RoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS."
"Torrent" = "Cấu hình sử dụng BitTorrent"
"TorrentDesc" = "Thay đổi mẫu cấu hình để tránh việc người dùng sử dụng BitTorrent."
"PrivateIp" = "Cấm 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"
"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 người lớn"
"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"
"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"
"IRIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Iran."
"IRDomain" = "Vô hiệu hóa kết nối đến tên miền của Iran"
"IRDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Iran."
"ChinaIp" = "Vô hiệu hóa kết nối đến dải IP của Trung Quốc"
"ChinaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Trung Quốc."
"ChinaDomain" = "Vô hiệu hóa kết nối đến tên miền của Trung Quốc"
"ChinaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Trung Quốc."
"RussiaIp" = "Vô hiệu hóa kết nối đến dải IP của Nga"
"RussiaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Nga."
"RussiaDomain" = "Vô hiệu hóa kết nối đến tên miền của Nga"
"RussiaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Nga."
"VNIp" = "Vô hiệu hóa kết nối đến dải IP của Việt Nam"
"VNIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến dải IP của Việt Nam."
"VNDomain" = "Vô hiệu hóa kết nối đến tên miền của Việt Nam"
"VNDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối đến các tên miền của Việt Nam."
"DirectIRIp" = "Kết nối trực tiếp đến dải IP của Iran"
"DirectIRIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Iran."
"DirectIRDomain" = "Kết nối trực tiếp đến tên miền của Iran"
"DirectIRDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Iran."
"DirectChinaIp" = "Kết nối trực tiếp đến dải IP của Trung Quốc"
"DirectChinaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Trung Quốc."
"DirectChinaDomain" = "Kết nối trực tiếp đến tên miền của Trung Quốc"
"DirectChinaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Trung Quốc."
"DirectRussiaIp" = "Kết nối trực tiếp đến dải IP của Nga"
"DirectRussiaIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Nga."
"DirectRussiaDomain" = "Kết nối trực tiếp đến tên miền của Nga"
"DirectRussiaDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Nga."
"DirectVNIp" = "Kết nối trực tiếp đến dải IP của Việt Nam"
"DirectVNIpDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến dải IP của Việt Nam"
"DirectVNDomain" = "Kết nối trực tiếp đến tên miền của Việt Nam"
"DirectVNDomainDesc" = "Thay đổi mẫu cấu hình cho kết nối trực tiếp đến các tên miền của Việt Nam."
"GoogleIPv4" = "Sử dụng IPv4 cho Google"
"GoogleIPv4Desc" = "Thêm định tuyến cho Google để kết nối qua IPv4."
"NetflixIPv4" = "Sử dụng IPv4 cho Netflix"
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối qua IPv4."
"GoogleWARP" = "Định tuyến Google qua WARP."
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
"OpenAIWARP" = "Định tuyến OpenAI (ChatGPT) qua WARP."
"OpenAIWARPDesc" = "Thêm định tuyến cho OpenAI (ChatGPT) qua WARP."
"NetflixWARP" = "Định tuyến Netflix qua WARP."
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
"SpotifyWARP" = "Định tuyến Spotify qua WARP."
"SpotifyWARPDesc" = "Thêm định tuyến cho Spotify 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."
"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ể."
"Outbounds" = "Đầu ra"
"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"
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
"completeTemplate" = "All"
[pages.xray.rules]
"first" = "Đầu tiên"
@@ -389,7 +400,7 @@
"source" = "Nguồn"
"dest" = "Đích"
"inbound" = "Vào"
"outbound" = "Ra ngoài"
"outbound" = "Ra"
"info" = "Thông tin"
"add" = "Thêm quy tắc"
"edit" = "Chỉnh sửa quy tắc"
@@ -410,6 +421,14 @@
"portal" = "Cổng thông tin"
"intercon" = "Kết nối"
[pages.xray.wireguard]
"secretKey" = "Chìa 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.settings.security]
"admin" = "Quản trị viên"
"secret" = "Mã thông báo bí mật"
@@ -433,14 +452,17 @@
"noIpRecord" = "❗ Không có bản ghi IP!"
"noInbounds" = "❗ Không tìm thấy inbound!"
"unlimited" = "♾ Không giới hạn"
"add" = "Thêm"
"month" = "Tháng"
"months" = "Tháng"
"day" = "Ngày"
"days" = "Ngày"
"hours" = "Giờ"
"unknown" = "Không rõ"
"inbounds" = "Inbounds"
"clients" = "Khách hàng"
"inbounds" = "Vào"
"clients" = "Các khách hàng"
"offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến"
[tgbot.commands]
"unknown" = "❗ Lệnh không rõ"
@@ -451,8 +473,8 @@
"status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
"helpAdminCommands" = "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."
"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 [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%"
@@ -467,7 +489,7 @@
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\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"
"serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n"
@@ -481,8 +503,9 @@
"port" = "🔌 Cổng: {{ .Port }}\r\n"
"expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n"
"expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n"
"active" = "💡 Hoạt động: ✅ Có\r\n"
"inactive" = "💡 Hoạt động: ❌ Không\r\n"
"active" = "💡 Đang hoạt động: {{ .Enable }}\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"
"upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n"
"download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n"
@@ -490,10 +513,13 @@
"TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Sự 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"
"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"
"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]
"closeKeyboard" = "❌ Đóng Bàn Phím"
@@ -503,11 +529,13 @@
"confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?"
"confirmClearIps" = "✅ Xác Nhận Xóa Các IP?"
"confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?"
"dbBackup" = "Tải Backup DB"
"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"
"serverUsage" = "Sử Dụng Máy Chủ"
"getInbounds" = "Lấy Inbounds"
"depleteSoon" = "Sắp Cạn Kiệt"
"getInbounds" = "Lấy cổng vào"
"depleteSoon" = "Depleted Soon"
"clientUsage" = "Lấy Sử Dụng"
"onlines" = "Khách hàng trực tuyến"
"commands" = "Lệnh"
"refresh" = "🔄 Cập Nhật"
"clearIPs" = "❌ Xóa IP"
@@ -515,14 +543,16 @@
"selectTGUser" = "👤 Chọn Người Dùng Telegram"
"selectOneTGUser" = "👤 Chọn một người dùng telegram:"
"resetTraffic" = "📈 Đặt Lại Lưu Lượng"
"resetExpire" = "📅 Đặt Lại Ngày Hết Hạn"
"ipLog" = "🔢 Log IP"
"ipLimit" = "🔢 Giới Hạn IP"
"resetExpire" = "📅 Thay đổi ngày hết hạn"
"ipLog" = "🔢 Nhật ký địa chỉ IP"
"ipLimit" = "🔢 Giới Hạn địa chỉ IP"
"setTGUser" = "👤 Đặt Người Dùng Telegram"
"toggle" = "🔘 Bật / Tắt"
"custom" = "🔢 Phong tục"
"confirmNumber" = "✅ Xác nhận : {{ .Num }}"
"limitTraffic" = "🚧 Giới hạn giao thông"
"custom" = "🔢 Tùy chỉnh"
"confirmNumber" = "✅ Xác nhận: {{ .Num }}"
"confirmNumberAdd" = "✅ Xác nhận thêm: {{ .Num }}"
"limitTraffic" = "🚧 Giới hạn lưu lượng"
"getBanLogs" = "Cấm nhật ký"
[tgbot.answers]
"successfulOperation" = "✅ Thành công!"
@@ -536,11 +566,10 @@
"setTrafficLimitSuccess" = "✅ {{ .Email }} : Đã lưu thành công giới hạn lưu lượng."
"expireResetSuccess" = "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công."
"resetIpSuccess" = "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công."
"clearIpSuccess" = "✅ {{ .Email }} : IPs Đã Được Xóa Thành Công."
"getIpLog" = "✅ {{ .Email }} : Lấy Log IP Thành Công."
"clearIpSuccess" = "✅ {{ .Email }} : IP Đã Được Xóa Thành Công."
"getIpLog" = "✅ {{ .Email }} : Lấy nhật ký IP Thành Công."
"getUserInfo" = "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram 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."
"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>"
"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>"
"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>"

View File

@@ -10,8 +10,8 @@
"remark" = "备注"
"enable" = "启用"
"protocol" = "协议"
"search" = "搜"
"filter" = "过滤器"
"search" = "搜"
"filter" = "筛选"
"loading" = "加载中..."
"second" = "秒"
"minute" = "分钟"
@@ -30,8 +30,8 @@
"sure" = "确定"
"encryption" = "加密"
"transmission" = "传输"
"host" = "主持人"
"path" = "小路"
"host" = "Host"
"path" = "Path"
"camouflage" = "伪装"
"status" = "状态"
"enabled" = "开启"
@@ -49,8 +49,8 @@
"install" = "安装"
"clients" = "客户端"
"usage" = "用法"
"secretToken" = "秘密令牌"
"remained" = "仍然存在"
"secretToken" = "安全密钥"
"remained" = "剩余"
"security" = "安全"
[menu]
@@ -59,10 +59,10 @@
"settings" = "面板设置"
"xray" = "Xray 设置"
"logout" = "退出登录"
"link" = "其他"
"link" = "管理"
[pages.login]
"title" = "登录"
"title" = "欢迎"
"loginAgain" = "登录时效已过,请重新登录"
[pages.login.toasts]
@@ -79,7 +79,7 @@
"xrayStatus" = "状态"
"stopXray" = "停止"
"restartXray" = "重启"
"xraySwitch" = "切换版本"
"xraySwitch" = "版本"
"xraySwitchClick" = "点击你想切换的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容"
"operationHours" = "系统正常运行时间"
@@ -134,7 +134,7 @@
"destinationPort" = "目标端口"
"targetAddress" = "目标地址"
"monitorDesc" = "默认留空即可"
"meansNoLimit" = "表示不限制"
"meansNoLimit" = " = 无限制单位GB)"
"totalFlow" = "总流量"
"leaveBlankToNeverExpire" = "留空则永不到期"
"noRecommendKeepDefault" = "没有特殊需求保持默认即可"
@@ -148,7 +148,7 @@
"client" = "客户"
"export" = "导出链接"
"clone" = "克隆"
"cloneInbound" = "创造"
"cloneInbound" = "克隆"
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
"cloneInboundOk" = "从创建克隆"
"resetAllTraffic" = "重置所有入站流量"
@@ -173,7 +173,7 @@
"setDefaultCert" = "从面板设置证书"
"xtlsDesc" = "Xray核心需要1.7.5"
"realityDesc" = "Xray核心需要1.8.0及以上版本"
"telegramDesc" = "使用 Telegram ID不包含 @ 符号或聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"telegramDesc" = "使用聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
"info" = "信息"
"same" = "相同"
@@ -195,26 +195,27 @@
"prefix" = "前缀"
"postfix" = "后缀"
"delayedStart" = "首次使用后开始"
"expireDays" = "过期天数"
"expireDays" = "期间"
"days" = "天"
"renew" = "自动续订"
"renewDesc" = "期后自动续订。0 = 禁用"
"renewDesc" = "期后自动续订。(0 = 禁用)(单元: 天)"
[pages.inbounds.toasts]
"obtain" = "获取"
[pages.inbounds.stream.general]
"requestHeader" = "请求头"
"name" = "名称"
"value" = ""
"request" = "要求"
"response" = "回复"
"name" = "姓名"
"value" = "价值"
[pages.inbounds.stream.tcp]
"requestVersion" = "请求版本"
"requestMethod" = "请求方法"
"requestPath" = "请求路径"
"responseVersion" = "响应版本"
"responseStatus" = "响应状态"
"responseStatusDescription" = "响应状态说明"
"version" = "版本"
"method" = "方法"
"path" = "小路"
"status" = "地位"
"statusDescription" = "状态说明"
"requestHeader" = "请求头"
"responseHeader" = "响应头"
[pages.inbounds.stream.quic]
@@ -246,6 +247,8 @@
"pageSize" = "分页大小"
"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用"
"remarkModel" = "备注模型和分隔符"
"datepicker" = "日期选择器"
"datepickerDescription" = "选择器日历类型指定到期日期"
"sampleRemark" = "备注示例"
"oldUsername" = "原用户名"
"currentPassword" = "原密码"
@@ -255,6 +258,8 @@
"telegramBotEnableDesc" = "重启面板生效"
"telegramToken" = "电报机器人TOKEN"
"telegramTokenDesc" = "重启面板生效"
"telegramProxy" = "Socks5 代理"
"telegramProxyDesc" = "如果您需要 Socks5 代理来连接 Telegram。 根据指南调整其设置。"
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。"
"telegramNotifyTime" = "电报机器人通知时间"
@@ -315,74 +320,80 @@
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
"warpConfigs" = "WARP 配置"
"warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在您的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。"
"xrayConfigTemplate" = "Xray 配置模板"
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的Xray配置文件重新启动面板生成效率"
"xrayConfigFreedomStrategy" = "配置自由协议的策略"
"xrayConfigFreedomStrategyDesc" = "在自由协议中设置网络输出策略"
"xrayConfigRoutingStrategy" = "配置路由域策略"
"xrayConfigRoutingStrategyDesc" = "设置DNS解析的整体路由策略"
"xrayConfigTorrent" = "禁止使用 bittorrent"
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent"
"xrayConfigPrivateIp" = "禁止私人 IP 范围连接"
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
"xrayConfigAds" = "屏蔽广告"
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告"
"xrayConfigFamily" = "启用家庭友好配置"
"xrayConfigFamilyDesc" = "避免为家人连接到不安全的网站"
"xrayConfigSpeedtest" = "阻止测速网站"
"xrayConfigSpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
"xrayConfigIRIpDesc" = "改配置模板避免连接伊朗IP段"
"xrayConfigIRDomain" = "禁止伊朗连接"
"xrayConfigIRDomainDesc" = "改配置模板避免连接伊朗域名"
"xrayConfigChinaIp" = "禁止中国 IP 范围连接"
"xrayConfigChinaIpDesc" = "改配置模板避免连接中国IP段"
"xrayConfigChinaDomain" = "禁止中国域名连接"
"xrayConfigChinaDomainDesc" = "改配置模板避免连接中国"
"xrayConfigRussiaIp" = "禁止俄罗斯 IP 范围连接"
"xrayConfigRussiaIpDesc" = "改配置模板避免连接俄罗斯IP范围"
"xrayConfigRussiaDomain" = "禁止俄罗斯连接"
"xrayConfigRussiaDomainDesc" = "改配置模板避免连接俄罗斯"
"xrayConfigDirectIRIp" = "直接连接到伊朗 IP 范围"
"xrayConfigDirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
"xrayConfigDirectIRDomain" = "直接连接到伊朗域"
"xrayConfigDirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
"xrayConfigDirectChinaIp" = "直连中国IP范围"
"xrayConfigDirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
"xrayConfigDirectChinaDomain" = "直连中国域名"
"xrayConfigDirectChinaDomainDesc" = "修改中国域名直连配置模板"
"xrayConfigDirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
"xrayConfigDirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
"xrayConfigDirectRussiaDomain" = "直接连接到俄罗斯域"
"xrayConfigDirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
"xrayConfigGoogleIPv4" = "为谷歌使用 IPv4"
"xrayConfigGoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
"xrayConfigNetflixIPv4" = "为 Netflix 使用 IPv4"
"xrayConfigNetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
"xrayConfigGoogleWARP" = "将谷歌路由到 WARP"
"xrayConfigGoogleWARPDesc" = "为谷歌添加路由到WARP"
"xrayConfigOpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
"xrayConfigOpenAIWARPDesc" = "将OpenAIChatGPT路由添加到WARP"
"xrayConfigNetflixWARP" = "将 Netflix 路由到 WARP"
"xrayConfigNetflixWARPDesc" = "为Netflix添加路由到WARP"
"xrayConfigSpotifyWARP" = "将 Spotify 路由到 WARP"
"xrayConfigSpotifyWARPDesc" = "为Spotify添加路由到WARP"
"xrayConfigIRWARP" = "将伊朗域名路由到 WARP"
"xrayConfigIRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
"xrayConfigInbounds" = "入站配置"
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端"
"xrayConfigOutbounds" = "出站配置"
"xrayConfigOutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"xrayConfigRoutings" = "路由规则配置"
"xrayConfigRoutingsDesc" = "更改配置模板为该服务器定义路由规则"
"completeTemplate" = "全部"
"Inbounds" = "界内"
"Template" = "Xray 配置模板"
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件重新启动面板生成效率"
"FreedomStrategy" = "配置自由协议的策略"
"FreedomStrategyDesc" = "在自由协议中设置网络输出策略"
"RoutingStrategy" = "配置路由域策略"
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略"
"Torrent" = "禁止使用 bittorrent"
"TorrentDesc" = "更改配置模板避免用户使用bittorrent"
"PrivateIp" = "禁止私人 IP 范围连接"
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
"Ads" = "屏蔽广告"
"AdsDesc" = "修改配置模板屏蔽广告"
"Family" = "阻止恶意软件和成人内容"
"FamilyDesc" = "Cloudflare DNS 解析器可阻止恶意软件和成人内容以保护家庭."
"Security" = "阻止恶意软件、网络钓鱼和加密货币挖矿网站"
"SecurityDesc" = "更改安全防护配置模板."
"Speedtest" = "阻止测速网站"
"SpeedtestDesc" = "改配置模板避免连接到速度测试网站。 重新启动面板以应用更改。"
"IRIp" = "禁止伊朗 IP 范围连接"
"IRIpDesc" = "改配置模板避免连接伊朗IP段"
"IRDomain" = "禁止伊朗域连接"
"IRDomainDesc" = "改配置模板避免连接伊朗域名"
"ChinaIp" = "禁止中国 IP 范围连接"
"ChinaIpDesc" = "改配置模板避免连接中国IP段"
"ChinaDomain" = "禁止中国域名连接"
"ChinaDomainDesc" = "改配置模板避免连接中国域"
"RussiaIp" = "禁止俄罗斯 IP 范围连接"
"RussiaIpDesc" = "改配置模板避免连接俄罗斯IP范围"
"RussiaDomain" = "禁止俄罗斯域连接"
"RussiaDomainDesc" = "更改配置模板避免连接俄罗斯域"
"VNIp" = "禁用与越南 IP 的连接"
"VNIpDesc" = "更改配置模板以避免连接到越南 IP 范围"
"VNDomain" = "禁用与越南域的连接"
"VNDomainDesc" = "更改配置模板以避免连接到越南域"
"DirectIRIp" = "直接连接到伊朗 IP 范围"
"DirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
"DirectIRDomain" = "直接连接到伊朗域"
"DirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
"DirectChinaIp" = "直连中国IP范围"
"DirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
"DirectChinaDomain" = "直连中国域名"
"DirectChinaDomainDesc" = "修改中国域名直连配置模板"
"DirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
"DirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
"DirectRussiaDomain" = "直接连接到俄罗斯域"
"DirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
"DirectVNIp" = "直接连接越南IP"
"DirectVNIpDesc" = "更改直接连接到越南 IP 范围的配置模板"
"DirectVNDomain" = "直接连接至越南域名"
"DirectVNDomainDesc" = "更改直连越南域的配置模板。"
"GoogleIPv4" = "为谷歌使用 IPv4"
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
"NetflixIPv4" = "为 Netflix 使用 IPv4"
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
"GoogleWARP" = "将谷歌路由到 WARP"
"GoogleWARPDesc" = "为谷歌添加路由到WARP"
"OpenAIWARP" = "将 OpenAI (ChatGPT) 路由到 WARP"
"OpenAIWARPDesc" = "将OpenAIChatGPT路由添加到WARP"
"NetflixWARP" = "将 Netflix 路由到 WARP"
"NetflixWARPDesc" = "为Netflix添加路由到WARP"
"SpotifyWARP" = "将 Spotify 路由到 WARP"
"SpotifyWARPDesc" = "为Spotify添加路由到WARP"
"IRWARP" = "将伊朗域名路由到 WARP"
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效"
"Inbounds" = "入站"
"InboundsDesc" = "更改配置模板接受特殊客户端"
"Outbounds" = "出站"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要"
"completeTemplate" = "全部"
[pages.xray.rules]
"firsto" = "第一个"
"first" = "第一个"
"last" = "最后"
"up" = "向上"
"down" = "向下"
@@ -401,15 +412,23 @@
"editOutbound" = "编辑出站"
"editReverse" = "编辑反向"
"tag" = "标签"
"tagDesc" = "独特的标签"
"tagDesc" = "唯一标记"
"address" = "地址"
"rreverse" = "反转"
"reverse" = "反转"
"domain" = "域名"
"type" = "类型"
"bridge" = "桥"
"portal" = "门户"
"intercon" = "互连"
[pages.xray.wireguard]
"secretKey" = "密钥"
"publicKey" = "公钥"
"allowedIPs" = "允许的 IP"
"endpoint" = "终点"
"psk" = "共享密钥"
"domainStrategy" = "域策略"
[pages.settings.security]
"admin" = "管理员"
"secret" = "密钥"
@@ -433,6 +452,7 @@
"noIpRecord" = "❗ 没有IP记录"
"noInbounds" = "❗ 没有找到入站连接!"
"unlimited" = "♾ 无限制"
"add" = "添加"
"month" = "月"
"months" = "月"
"day" = "天"
@@ -441,6 +461,8 @@
"unknown" = "未知"
"inbounds" = "入站连接"
"clients" = "客户端"
"offline" = "🔴 离线"
"online" = "🟢 在线的"
[tgbot.commands]
"unknown" = "❗ 未知命令"
@@ -451,8 +473,8 @@
"status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的ID为<code>{{ .ID }}</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请使用密码。"
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n\r\n搜索入站连接包含客户端统计信息\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%"
@@ -482,7 +504,8 @@
"expire" = "📅 过期日期:{{ .Time }}\r\n"
"expireIn" = "📅 剩余时间:{{ .Time }}\r\n"
"active" = "💡 激活:✅\r\n"
"inactive" = "💡 激活: ❌\r\n"
"enabled" = "🚨 已启用:{{ .Enable }}\r\n"
"online" = "🌐 连接状态:{{ .Status }}\r\n"
"email" = "📧 邮箱:{{ .Email }}\r\n"
"upload" = "🔼 上传↑:{{ .Upload }}\r\n"
"download" = "🔽 下载↓:{{ .Download }}\r\n"
@@ -490,10 +513,13 @@
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}\r\n"
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n"
"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n"
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n \r\n"
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\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]
"closeKeyboard" = "❌ 关闭键盘"
@@ -503,11 +529,13 @@
"confirmResetTraffic" = "✅ 确认重置流量?"
"confirmClearIps" = "✅ 确认清除 IP"
"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?"
"confirmToggle" = "✅ 确认启用/禁用用户?"
"dbBackup" = "获取数据库备份"
"serverUsage" = "服务器使用情况"
"getInbounds" = "获取入站信息"
"depleteSoon" = "即将耗尽"
"clientUsage" = "获取使用情况"
"onlines" = "在线客户"
"commands" = "命令"
"refresh" = "🔄 刷新"
"clearIPs" = "❌ 清除 IP"
@@ -515,14 +543,16 @@
"selectTGUser" = "👤 选择 Telegram 用户"
"selectOneTGUser" = "👤 选择一个 Telegram 用户:"
"resetTraffic" = "📈 重置流量"
"resetExpire" = "📅 重置过期天数"
"resetExpire" = "📅 更改到期日期"
"ipLog" = "🔢 IP 日志"
"ipLimit" = "🔢 IP 限制"
"setTGUser" = "👤 设置 Telegram 用户"
"toggle" = "🔘 启用/禁用"
"custom" = "🔢 风俗"
"confirmNumber" = "✅ 确认 : {{ .Num }}"
"confirmNumber" = "✅ 确认: {{ .Num }}"
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
"limitTraffic" = "🚧 交通限制"
"getBanLogs" = "禁止日志"
[tgbot.answers]
"successfulOperation" = "✅ 成功的!"
@@ -542,5 +572,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用户已成功移除。"
"enableSuccess" = "✅ {{ .Email }}:已成功启用。"
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID<b>{{ .TgUserID }}</b>"
"askToAddUserName" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户名或用户ID。\r\n\r\n您的用户名<b>@{{ .TgUserName }}</b>\r\n\r\n您的用户ID<b>{{ .TgUserID }}</b>"
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID<code>{{ .TgUserID }}</code>"

View File

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

173
x-ui.sh
View File

@@ -51,11 +51,23 @@ elif [[ "${release}" == "fedora" ]]; then
echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1
fi
elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 10 ]]; then
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
if [[ ${os_version} -lt 11 ]]; then
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
elif [[ "${release}" == "arch" ]]; then
echo "OS is ArchLinux"
echo "Your OS is ArchLinux"
elif [[ "${release}" == "manjaro" ]]; then
echo "Your OS is Manjaro"
elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian"
fi
@@ -107,7 +119,7 @@ install() {
}
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
LOGE "Cancelled"
if [[ $# == 0 ]]; then
@@ -122,6 +134,24 @@ update() {
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
}
uninstall() {
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
if [[ $? != 0 ]]; then
@@ -321,7 +351,7 @@ enable_bbr() {
ubuntu|debian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
;;
centos)
centos|almalinux|rocky)
yum -y update && yum -y install ca-certificates
;;
fedora)
@@ -519,11 +549,13 @@ update_geo() {
systemctl stop x-ui
cd ${binFolder}
rm -f geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat
rm -f geoip.dat geosite.dat geoip_IR.dat geosite_IR.dat geoip_VN.dat geosite_VN.dat
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -N https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat -N https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
systemctl start x-ui
echo -e "${green}Geosite.dat + Geoip.dat + geoip_IR.dat + geosite_IR.dat have been updated successfully in bin folder '${binfolder}'!${plain}"
before_show_menu
@@ -579,9 +611,9 @@ ssl_cert_issue() {
fi
# install socat second
case "${release}" in
ubuntu|debian)
ubuntu|debian|armbian)
apt update && apt install socat -y ;;
centos)
centos|almalinux|rocky)
yum -y update && yum -y install socat ;;
fedora)
dnf -y update && dnf -y install socat ;;
@@ -768,6 +800,33 @@ warp_cloudflare() {
esac
}
multi_protocol() {
echo "This script only supports Vless and Vmess. if you use another protocols, DON'T INSTALL or get backup first! "
echo -e "${green}\t1.${plain} Install Multi Protocol Script"
echo -e "${green}\t2.${plain} Uninstall"
echo -e "${green}\t3.${plain} Start Service"
echo -e "${green}\t4.${plain} Stop Service"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu ;;
1)
bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/install.sh --ipv4)
;;
2)
bash <(curl -Ls https://raw.githubusercontent.com/M4mmad/3xui-multi-protocol/master/unistall.sh --ipv4)
;;
3)
systemctl start 3xui-multi-protocol
;;
4)
systemctl stop 3xui-multi-protocol
;;
*) echo "Invalid choice" ;;
esac
}
run_speedtest() {
# Check if Speedtest is already installed
if ! command -v speedtest &> /dev/null; then
@@ -924,7 +983,7 @@ install_iplimit() {
case "${release}" in
ubuntu|debian)
apt update && apt install fail2ban -y ;;
centos)
centos|almalinux|rocky)
yum -y update && yum -y install fail2ban ;;
fedora)
dnf -y update && dnf -y install fail2ban ;;
@@ -987,7 +1046,7 @@ remove_iplimit(){
case "${release}" in
ubuntu|debian)
apt-get purge fail2ban -y;;
centos)
centos|almalinux|rocky)
yum remove fail2ban -y;;
fedora)
dnf remove fail2ban -y;;
@@ -1029,36 +1088,38 @@ show_menu() {
${green}3X-ui Panel Management Script${plain}
${green}0.${plain} Exit Script
————————————————
${green}1.${plain} Install x-ui
${green}2.${plain} Update x-ui
${green}3.${plain} Uninstall x-ui
${green}1.${plain} Install
${green}2.${plain} Update
${green}3.${plain} Custom Version
${green}4.${plain} Uninstall
————————————————
${green}4.${plain} Reset Username & Password & Secret Token
${green}5.${plain} Reset Panel Settings
${green}6.${plain} Change Panel Port
${green}7.${plain} View Current Panel Settings
${green}5.${plain} Reset Username & Password & Secret Token
${green}6.${plain} Reset Settings
${green}7.${plain} Change Port
${green}8.${plain} View Current Settings
————————————————
${green}8.${plain} Start x-ui
${green}9.${plain} Stop x-ui
${green}10.${plain} Restart x-ui
${green}11.${plain} Check x-ui Status
${green}12.${plain} Check x-ui Logs
${green}9.${plain} Start
${green}10.${plain} Stop
${green}11.${plain} Restart
${green}12.${plain} Check Status
${green}13.${plain} Check Logs
————————————————
${green}13.${plain} Enable x-ui On System Startup
${green}14.${plain} Disable x-ui On System Startup
${green}14.${plain} Enable x-ui On System Startup
${green}15.${plain} Disable x-ui On System Startup
————————————————
${green}15.${plain} SSL Certificate Management
${green}16.${plain} Cloudflare SSL Certificate
${green}17.${plain} IP Limit Management
${green}18.${plain} WARP Management
${green}16.${plain} SSL Certificate Management
${green}17.${plain} Cloudflare SSL Certificate
${green}18.${plain} IP Limit Management
${green}19.${plain} WARP Management
${green}20.${plain} Multi Protocol Management
————————————————
${green}19.${plain} Enable BBR
${green}20.${plain} Update Geo Files
${green}21.${plain} Active Firewall and open ports
${green}22.${plain} Speedtest by Ookla
${green}21.${plain} Enable BBR
${green}22.${plain} Update Geo Files
${green}23.${plain} Active Firewall and open ports
${green}24.${plain} Speedtest by Ookla
"
show_status
echo && read -p "Please enter your selection [0-22]: " num
echo && read -p "Please enter your selection [0-24]: " num
case "${num}" in
0)
@@ -1071,67 +1132,73 @@ show_menu() {
check_install && update
;;
3)
check_install && uninstall
check_install && custom_version
;;
4)
check_install && reset_user
check_install && uninstall
;;
5)
check_install && reset_config
check_install && reset_user
;;
6)
check_install && set_port
check_install && reset_config
;;
7)
check_install && check_config
check_install && set_port
;;
8)
check_install && start
check_install && check_config
;;
9)
check_install && stop
check_install && start
;;
10)
check_install && restart
check_install && stop
;;
11)
check_install && status
check_install && restart
;;
12)
check_install && show_log
check_install && status
;;
13)
check_install && enable
check_install && show_log
;;
14)
check_install && disable
check_install && enable
;;
15)
ssl_cert_issue_main
check_install && disable
;;
16)
ssl_cert_issue_CF
ssl_cert_issue_main
;;
17)
iplimit_main
ssl_cert_issue_CF
;;
18)
warp_cloudflare
iplimit_main
;;
19)
enable_bbr
warp_cloudflare
;;
20)
update_geo
multi_protocol
;;
21)
open_ports
enable_bbr
;;
22)
update_geo
;;
23)
open_ports
;;
24)
run_speedtest
;;
*)
LOGE "Please enter the correct number [0-22]"
LOGE "Please enter the correct number [0-24]"
;;
esac
}

View File

@@ -50,7 +50,9 @@ func (x *XrayAPI) Init(apiPort int) (err error) {
}
func (x *XrayAPI) Close() {
x.grpcClient.Close()
if x.grpcClient != nil {
x.grpcClient.Close()
}
x.HandlerServiceClient = nil
x.StatsServiceClient = nil
x.isConnected = false

View File

@@ -20,8 +20,13 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
lw.lastLine = messages[len(messages)-1]
for _, msg := range messages {
messageBody := msg
// Remove timestamp
messageBody := strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
splittedMsg := strings.SplitN(msg, " ", 3)
if len(splittedMsg) > 2 {
messageBody = strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
}
// Find level in []
startIndex := strings.Index(messageBody, "[")

View File

@@ -41,12 +41,24 @@ func GetIPLimitLogPath() string {
return config.GetLogFolder() + "/3xipl.log"
}
func GetIPLimitPrevLogPath() string {
return config.GetLogFolder() + "/3xipl.prev.log"
}
func GetIPLimitBannedLogPath() string {
return config.GetLogFolder() + "/3xipl-banned.log"
}
func GetIPLimitBannedPrevLogPath() string {
return config.GetLogFolder() + "/3xipl-banned.prev.log"
}
func GetAccessPersistentLogPath() string {
return config.GetLogFolder() + "/3xipl-access-persistent.log"
return config.GetLogFolder() + "/3xipl-ap.log"
}
func GetAccessPersistentPrevLogPath() string {
return config.GetLogFolder() + "/3xipl-ap.prev.log"
}
func GetAccessLogPath() string {