mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 17:15:49 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236dddf482 | ||
|
|
8e472838d8 | ||
|
|
b75a1ef5e1 | ||
|
|
d956f78347 | ||
|
|
8ef447a997 | ||
|
|
520f7a2d15 | ||
|
|
3ded4ee658 | ||
|
|
bea19a263d | ||
|
|
878e0d02cd | ||
|
|
b15ea1f74d | ||
|
|
431d7350a5 | ||
|
|
127bea7f73 | ||
|
|
7c58bcbb46 | ||
|
|
fec9b25248 | ||
|
|
728166bd1a | ||
|
|
d376ce057c | ||
|
|
5e6e900e64 | ||
|
|
19f7938617 | ||
|
|
fe791b6e99 | ||
|
|
a02bf3195d | ||
|
|
3ea05d30c1 | ||
|
|
40ebf2902e | ||
|
|
14253c3586 | ||
|
|
7b15274c84 | ||
|
|
6545d8b61d | ||
|
|
6f4eefe601 | ||
|
|
510c35f450 | ||
|
|
00addb0dd9 | ||
|
|
db140a1e9b | ||
|
|
db945e2fbd | ||
|
|
667fac15f4 | ||
|
|
29033a7828 | ||
|
|
2ffde55f8f | ||
|
|
6e5ed881f2 | ||
|
|
d52c50fd9e | ||
|
|
fa5fb927c1 | ||
|
|
f7198c4c2f | ||
|
|
21e7d45b54 | ||
|
|
db62a07fb8 | ||
|
|
e3120c4028 | ||
|
|
7ae855e7c9 | ||
|
|
b9307c6c9c | ||
|
|
d30cdbf49a | ||
|
|
cac00224db | ||
|
|
b68f0a206c | ||
|
|
0bde51b91e | ||
|
|
280a22b57d | ||
|
|
315d852087 | ||
|
|
6a0d2e0a29 |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- 386
|
- 386
|
||||||
- armv5
|
- armv5
|
||||||
- s390x
|
- s390x
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.3.6/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.3.31/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
|
|||||||
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1,16 +1,37 @@
|
|||||||
.idea
|
# Ignore editor and IDE settings
|
||||||
.vscode
|
.idea/
|
||||||
.cache
|
.vscode/
|
||||||
|
.cache/
|
||||||
.sync*
|
.sync*
|
||||||
*.tar.gz
|
|
||||||
|
# Ignore log files
|
||||||
*.log
|
*.log
|
||||||
access.log
|
|
||||||
error.log
|
# Ignore temporary files
|
||||||
tmp
|
tmp/
|
||||||
main
|
*.tar.gz
|
||||||
|
|
||||||
|
# Ignore build and distribution directories
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
release/
|
release/
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Ignore compiled binaries
|
||||||
|
main
|
||||||
|
|
||||||
|
# Ignore script and executable files
|
||||||
/release.sh
|
/release.sh
|
||||||
/x-ui
|
/x-ui
|
||||||
|
|
||||||
|
# Ignore OS specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Ignore Go specific files
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
|
||||||
|
# Ignore Docker specific files
|
||||||
|
docker-compose.override.yml
|
||||||
@@ -27,7 +27,7 @@ case $1 in
|
|||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.3.6/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.3.31/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|||||||
569
README.ar_EG.md
Normal file
569
README.ar_EG.md
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
||||||
|
</picture>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**لوحة تحكم ويب متقدمة • مبنية على Xray Core**
|
||||||
|
|
||||||
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
|
[](#)
|
||||||
|
[](#)
|
||||||
|
[](#)
|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
|
> **تنبيه:** المشروع ده للتعلم الشخصي والتواصل فقط. رجاءً استخدمه بشكل قانوني.
|
||||||
|
|
||||||
|
**لو المشروع عجبك وساعدك، ممكن تدعمنا بعمل** :star2:
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||||
|
<img src="./media/buymeacoffe.png" alt="Image">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
|
## التثبيت والتحديث
|
||||||
|
|
||||||
|
لتثبيت المشروع أو تحديثه، نفذ الأمر ده:
|
||||||
|
```bash
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
## تثبيت النسخة القديمة (مش موصى بيها)
|
||||||
|
|
||||||
|
لو عايز تثبت نسخة معينة، استخدم الأمر ده، مثلاً نسخة `v1.7.9`:
|
||||||
|
```bash
|
||||||
|
VERSION=v1.7.9 && bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
## شهادة SSL
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل شهادة SSL</summary>
|
||||||
|
|
||||||
|
### ACME
|
||||||
|
|
||||||
|
عشان تدير شهادات SSL باستخدام ACME:
|
||||||
|
|
||||||
|
1. تأكد إن الدومين بتاعك متربط صح بالسيرفر.
|
||||||
|
2. شغّل أمر `x-ui` في الترمينال واختار خيار "إدارة شهادات SSL".
|
||||||
|
3. هتلاقي الخيارات دي:
|
||||||
|
- **Get SSL:** الحصول على شهادة SSL.
|
||||||
|
- **Revoke:** إلغاء شهادة SSL موجودة.
|
||||||
|
- **Force Renew:** تجديد شهادة SSL بالقوة.
|
||||||
|
- **Show Existing Domains:** عرض كل شهادات الدومين المتوفرة على السيرفر.
|
||||||
|
- **Set Certificate Paths for the Panel:** تعيين مسارات الشهادة عشان البانل يستخدمها.
|
||||||
|
|
||||||
|
### Certbot
|
||||||
|
|
||||||
|
لتثبيت واستخدام Certbot:
|
||||||
|
```sh
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
|
السكريبت بتاع الإدارة فيه آلية مدمجة للتقديم على شهادة SSL من خلال Cloudflare. عشان تستخدمها، هتحتاج:
|
||||||
|
- بريد إلكتروني مسجل على Cloudflare.
|
||||||
|
- الـ Global API Key بتاع Cloudflare.
|
||||||
|
- الدومين لازم يكون مربوط للسيرفر الحالي عن طريق Cloudflare.
|
||||||
|
|
||||||
|
**كيفية الحصول على Global API Key من Cloudflare:**
|
||||||
|
|
||||||
|
1. شغّل أمر `x-ui` في الترمينال واختار "Cloudflare SSL Certificate".
|
||||||
|
2. ادخل على [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
|
||||||
|
3. دوس على "View Global API Key" (شوف الصورة التوضيحية):
|
||||||
|

|
||||||
|
4. يمكن تحتاج تعيد تسجيل الدخول، وبعدها هتظهر الـ API Key (شوف الصورة التوضيحية):
|
||||||
|

|
||||||
|
|
||||||
|
عند الاستخدام، ادخل اسم الدومين، البريد الإلكتروني، وAPI Key. المخطط كالتالي:
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## التثبيت والتحديث يدويًا
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل التثبيت اليدوي</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
1. لتحميل أحدث نسخة من الباكدج المظغوطة مباشرة على السيرفر، نفذ الأمر التالي:
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
s390x) echo 's390x' ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. بعد تحميل الباكدج، نفذ الأوامر دي للتثبيت أو التحديث:
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
s390x) echo 's390x' ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cd /root/
|
||||||
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||||
|
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||||
|
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||||
|
mv x-ui/ /usr/local/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl restart x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## التثبيت باستخدام Docker
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل Docker</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
1. **تثبيت Docker:**
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **نسخ مستودع المشروع:**
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/MHSanaei/3x-ui.git
|
||||||
|
cd 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **تشغيل الخدمة:**
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
ممكن تضيف الخيار ```--pull always``` عشان Docker يسحب أحدث صورة لو موجودة. (راجع [مستندات Docker](https://docs.docker.com/reference/cli/docker/container/run/#pull) للمزيد من التفاصيل).
|
||||||
|
|
||||||
|
**أو**
|
||||||
|
```sh
|
||||||
|
docker run -itd \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
|
-v $PWD/cert/:/root/cert/ \
|
||||||
|
--network=host \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
--name 3x-ui \
|
||||||
|
ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **التحديث إلى أحدث نسخة:**
|
||||||
|
```sh
|
||||||
|
cd 3x-ui
|
||||||
|
docker compose down
|
||||||
|
docker compose pull 3x-ui
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **إزالة 3x-ui من Docker:**
|
||||||
|
```sh
|
||||||
|
docker stop 3x-ui
|
||||||
|
docker rm 3x-ui
|
||||||
|
cd --
|
||||||
|
rm -r 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## إعدادات Nginx
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض إعدادات البروكسي العكسي (Reverse Proxy)</summary>
|
||||||
|
|
||||||
|
#### Reverse Proxy باستخدام Nginx
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Range $http_range;
|
||||||
|
proxy_set_header If-Range $http_if_range;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_pass http://127.0.0.1:2053;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### استخدام Nginx مع sub-path
|
||||||
|
- تأكد إن "URI Path" في إعدادات `/sub` للبانل متطابق.
|
||||||
|
- لازم يكون رابط `url` في إعدادات البانل منتهي بـ `/`.
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /sub {
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Range $http_range;
|
||||||
|
proxy_set_header If-Range $http_if_range;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_pass http://127.0.0.1:2053;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## نظام التشغيل الموصى به
|
||||||
|
|
||||||
|
- Ubuntu 22.04+
|
||||||
|
- Debian 12+
|
||||||
|
- CentOS 8+
|
||||||
|
- OpenEuler 22.03+
|
||||||
|
- Fedora 36+
|
||||||
|
- Arch Linux
|
||||||
|
- Parch Linux
|
||||||
|
- Manjaro
|
||||||
|
- Armbian
|
||||||
|
- AlmaLinux 9.5+
|
||||||
|
- Rocky Linux 9.5+
|
||||||
|
- Oracle Linux 8+
|
||||||
|
- OpenSUSE Tubleweed
|
||||||
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
|
- Windows x64
|
||||||
|
|
||||||
|
## المعماريات والأجهزة المدعومة
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل المعماريات والأجهزة المدعومة</summary>
|
||||||
|
|
||||||
|
منصتنا بتدعم مجموعة متنوعة من المعماريات والأجهزة عشان تناسب بيئات مختلفة. أبرز المعماريات هي:
|
||||||
|
|
||||||
|
- **amd64:** المعمارية القياسية للكمبيوترات الشخصية والسيرفرات.
|
||||||
|
- **x86 / i386:** مستخدمة على نطاق واسع في أجهزة الديسكتوب واللاب توب.
|
||||||
|
- **armv8 / arm64 / aarch64:** موجهة للأجهزة المحمولة والمضمنة زي Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS، وغيرها.
|
||||||
|
- **armv7 / arm / arm32:** للأجهزة المحمولة والأجهزة المضمنة القديمة، مثل Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2.
|
||||||
|
- **armv6 / arm / arm32:** للأجهزة المضمنة القديمة جداً، زي Raspberry Pi 1, Raspberry Pi Zero/Zero W.
|
||||||
|
- **armv5 / arm / arm32:** معمارية أقدم مرتبطة بالأنظمة المضمنة القديمة.
|
||||||
|
- **s390x:** مستخدمة في الحواسيب الرئيسية من IBM وتوفر أداء عالي واعتمادية للمشاريع الكبيرة.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## اللغات
|
||||||
|
|
||||||
|
- English (إنجليزي)
|
||||||
|
- Persian (فارسي)
|
||||||
|
- Traditional Chinese (الصيني التقليدي)
|
||||||
|
- Simplified Chinese (الصيني المبسط)
|
||||||
|
- Japanese (ياباني)
|
||||||
|
- Russian (روسي)
|
||||||
|
- Vietnamese (فيتنامي)
|
||||||
|
- Spanish (إسباني)
|
||||||
|
- Indonesian (إندونيسي)
|
||||||
|
- Ukrainian (أوكراني)
|
||||||
|
- Turkish (تركي)
|
||||||
|
- Português (Brazil) (برتغالي - البرازيل)
|
||||||
|
|
||||||
|
## الميزات
|
||||||
|
|
||||||
|
- مراقبة حالة النظام
|
||||||
|
- البحث داخل كل الإدخالات والعملاء
|
||||||
|
- ثيم داكن وفاتح
|
||||||
|
- دعم تعدد المستخدمين والبروتوكولات
|
||||||
|
- دعم بروتوكولات زي VMESS، VLESS، Trojan، Shadowsocks، Dokodemo-door، Socks، HTTP، wireguard
|
||||||
|
- دعم بروتوكولات XTLS الأصلية مثل RPRX-Direct، Vision، REALITY
|
||||||
|
- إحصائيات الترافيك، تحديد حد للترافيك، وتحديد فترة انتهاء الصلاحية
|
||||||
|
- قوالب إعدادات Xray قابلة للتخصيص
|
||||||
|
- دعم لوحة تحكم عبر HTTPS (بإستخدام الدومين الخاص وشهادة SSL)
|
||||||
|
- دعم التقديم على شهادات SSL بلمسة واحدة والتجديد التلقائي
|
||||||
|
- لمزيد من إعدادات التخصيص المتقدمة، راجع إعدادات البانل
|
||||||
|
- إصلاح مسارات الـ API (سيتم إنشاء إعدادات للمستخدم مع الـ API)
|
||||||
|
- دعم تغيير الإعدادات من خلال الخيارات المتوفرة في البانل
|
||||||
|
- دعم تصدير/استيراد قاعدة البيانات من خلال البانل
|
||||||
|
|
||||||
|
## إعدادات البانل الافتراضية
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل الإعدادات الافتراضية</summary>
|
||||||
|
|
||||||
|
### اسم المستخدم، الباسورد، البورت ومسار الويب الأساسي
|
||||||
|
|
||||||
|
لو مش هتعدل الإعدادات دي، هتتولد تلقائياً (ده مش بينطبق على Docker).
|
||||||
|
|
||||||
|
**الإعدادات الافتراضية لـ Docker:**
|
||||||
|
- **اسم المستخدم:** admin
|
||||||
|
- **الباسورد:** admin
|
||||||
|
- **البورت:** 2053
|
||||||
|
|
||||||
|
### إدارة قاعدة البيانات:
|
||||||
|
|
||||||
|
ممكن تعمل نسخ احتياطية واسترجاع لقاعدة البيانات مباشرة من البانل.
|
||||||
|
|
||||||
|
- **مسار قاعدة البيانات:**
|
||||||
|
- `/etc/x-ui/x-ui.db`
|
||||||
|
|
||||||
|
### المسار الأساسي للويب
|
||||||
|
|
||||||
|
1. **إعادة تعيين المسار الأساسي:**
|
||||||
|
- افتح الترمينال.
|
||||||
|
- نفذ أمر `x-ui`.
|
||||||
|
- اختار خيار "إعادة تعيين المسار الأساسي للويب".
|
||||||
|
|
||||||
|
2. **توليد أو تخصيص المسار:**
|
||||||
|
- المسار هيتولد تلقائياً، أو ممكن تدخل مسار مخصص.
|
||||||
|
|
||||||
|
3. **عرض الإعدادات الحالية:**
|
||||||
|
- لمشاهدة الإعدادات الحالية، نفذ أمر `x-ui settings` في الترمينال أو استخدم خيار "عرض الإعدادات الحالية" في البانل.
|
||||||
|
|
||||||
|
### توصية الأمان:
|
||||||
|
- لتحسين الأمان، استخدم كلمة طويلة وعشوائية في مسار URL الخاص بالبانل.
|
||||||
|
|
||||||
|
**مثال:**
|
||||||
|
- `http://ip:port/*webbasepath*/panel`
|
||||||
|
- `http://domain:port/*webbasepath*/panel`
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## إعداد WARP
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل إعداد WARP</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
**لإصدارات `v2.1.0` وما بعدها:**
|
||||||
|
|
||||||
|
WARP مدمج ومش محتاج تثبيت إضافي. فعل الإعدادات المطلوبة من خلال البانل.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## تحديد IP
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل تحديد IP</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
**ملحوظة:** تحديد IP ممكن مايشتغلش صح مع IP Tunnel.
|
||||||
|
|
||||||
|
- **للإصدارات حتى `v1.6.1`:**
|
||||||
|
- تحديد IP مدمج في البانل.
|
||||||
|
|
||||||
|
**للإصدارات `v1.7.0` وما بعدها:**
|
||||||
|
|
||||||
|
لتفعيل وظيفة تحديد IP، هتحتاج تثبيت `fail2ban` والملفات المطلوبة من خلال الخطوات دي:
|
||||||
|
|
||||||
|
1. شغل أمر `x-ui` في الترمينال واختار "إدارة تحديد IP".
|
||||||
|
2. هتلاقي الخيارات التالية:
|
||||||
|
- **تغيير مدة الحظر:** لتعديل مدة الحظر.
|
||||||
|
- **رفع الحظر عن الجميع:** لإلغاء كل الحظر الحالي.
|
||||||
|
- **عرض السجلات:** لمراجعة السجلات.
|
||||||
|
- **حالة Fail2ban:** لمراجعة حالة fail2ban.
|
||||||
|
- **إعادة تشغيل Fail2ban:** لإعادة تشغيل خدمة fail2ban.
|
||||||
|
- **إلغاء تثبيت Fail2ban:** لإلغاء تثبيت fail2ban مع إعداداته.
|
||||||
|
|
||||||
|
3. حدد مسار سجل الوصول على البانل من خلال ضبط `Xray Configs/log/Access log` على `./access.log` ثم احفظ وأعد تشغيل x-ui.
|
||||||
|
|
||||||
|
- **للإصدارات قبل `v2.1.3`:**
|
||||||
|
- هتحتاج تضبط مسار سجل الوصول يدويًا في إعدادات Xray:
|
||||||
|
```sh
|
||||||
|
"log": {
|
||||||
|
"access": "./access.log",
|
||||||
|
"dnsLog": false,
|
||||||
|
"loglevel": "warning"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- **للإصدارات `v2.1.3` وما بعدها:**
|
||||||
|
- في خيار لضبط `access.log` مباشرة من البانل.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## بوت Telegram
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل بوت Telegram</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
تدعم لوحة التحكم إشعارات بترافيك يومي، تسجيل الدخول للبانل، نسخ احتياطية للقاعدة، حالة النظام، معلومات العملاء، وغيرها من الوظائف عن طريق بوت Telegram. عشان تستخدم البوت، لازم تضبط معلمات البوت في البانل، ومن ضمنهم:
|
||||||
|
- توكن Telegram
|
||||||
|
- ID شات الأدمن (يمكن إدخال أكثر من واحد بفواصل)
|
||||||
|
- وقت الإشعار (باستخدام صيغة cron)
|
||||||
|
- إشعار بتاريخ انتهاء الصلاحية
|
||||||
|
- إشعار حد الترافيك
|
||||||
|
- نسخ احتياطية للقاعدة
|
||||||
|
- إشعار حمل المعالج
|
||||||
|
|
||||||
|
**صيغة مرجعية:**
|
||||||
|
|
||||||
|
- `30 * * * * *` - إشعار عند الـ 30 ثانية من كل دقيقة.
|
||||||
|
- `0 */10 * * * *` - إشعار عند أول ثانية من كل 10 دقايق.
|
||||||
|
- `@hourly` - إشعار كل ساعة.
|
||||||
|
- `@daily` - إشعار يومي (في تمام منتصف الليل).
|
||||||
|
- `@weekly` - إشعار أسبوعي.
|
||||||
|
- `@every 8h` - إشعار كل 8 ساعات.
|
||||||
|
|
||||||
|
### ميزات بوت Telegram
|
||||||
|
|
||||||
|
- تقارير دورية.
|
||||||
|
- إشعارات عند تسجيل الدخول.
|
||||||
|
- إشعار عند تجاوز حمل المعالج.
|
||||||
|
- تنبيهات قبل انتهاء الصلاحية أو وصول حد الترافيك.
|
||||||
|
- دعم قوائم تقارير العملاء لو تم إضافة اسم مستخدم Telegram للمستخدم في الإعدادات.
|
||||||
|
- إمكانية البحث عن تقرير الترافيك باستخدام UUID (VMESS/VLESS) أو الباسورد (TROJAN) بشكل مجهول.
|
||||||
|
- بوت يعتمد على القوائم.
|
||||||
|
- البحث عن العملاء بالإيميل (للأدمن فقط).
|
||||||
|
- استعراض كافة الإدخالات.
|
||||||
|
- عرض حالة السيرفر.
|
||||||
|
- استعراض العملاء المستنفدين.
|
||||||
|
- استقبال النسخ الاحتياطية عند الطلب وفي التقارير الدورية.
|
||||||
|
- بوت متعدد اللغات.
|
||||||
|
|
||||||
|
### إعداد بوت Telegram
|
||||||
|
|
||||||
|
- ابدأ [Botfather](https://t.me/BotFather) في حساب Telegram بتاعك:
|
||||||
|

|
||||||
|
|
||||||
|
- أنشئ بوت جديد باستخدام أمر /newbot: هيسألك سؤالين، اسم للبوت واسم مستخدم (لازم ينتهي بكلمة "bot").
|
||||||
|

|
||||||
|
|
||||||
|
- شغل البوت اللي أنشأته. هتلاقي رابط البوت بعد كده.
|
||||||
|

|
||||||
|
|
||||||
|
- ادخل على البانل واضبط إعدادات بوت Telegram زي ما هو موضح:
|
||||||
|

|
||||||
|
|
||||||
|
ادخل توكن البوت في الحقل رقم 3.
|
||||||
|
ادخل ID المستخدم في الحقل رقم 4. الحسابات اللي بالـ ID ده هيبقى ليها صلاحية الأدمن. (يمكن إدخال أكثر من واحد بفواصل)
|
||||||
|
|
||||||
|
- كيفية الحصول على ID حساب Telegram؟ استخدم [هذا البوت](https://t.me/useridinfobot). شغله وهيدي الـ ID بتاعك.
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## مسارات API
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل مسارات API</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
- [توثيق الـ API](https://www.postman.com/hsanaei/3x-ui/collection/q1l5l0u/3x-ui)
|
||||||
|
- `/login` مع بيانات المستخدم باستخدام `POST`: `{username: '', password: ''}` لتسجيل الدخول.
|
||||||
|
- المسار الأساسي لـ `/panel/api/inbounds` للعمليات التالية:
|
||||||
|
|
||||||
|
| الطريقة | المسار | الفعل |
|
||||||
|
| :-----: | ------------------------------------ | ------------------------------------------ |
|
||||||
|
| `GET` | `"/list"` | استرجاع كل الإدخالات |
|
||||||
|
| `GET` | `"/get/:id"` | استرجاع إدخال بالـ id |
|
||||||
|
| `GET` | `"/getClientTraffics/:email"` | استرجاع ترافيك عميل بالإيميل |
|
||||||
|
| `GET` | `"/getClientTrafficsById/:id"` | استرجاع ترافيك عميل بالـ id |
|
||||||
|
| `GET` | `"/createbackup"` | البوت بيرسل نسخة احتياطية للأدمن |
|
||||||
|
| `POST` | `"/add"` | إضافة إدخال |
|
||||||
|
| `POST` | `"/del/:id"` | حذف إدخال |
|
||||||
|
| `POST` | `"/update/:id"` | تحديث إدخال |
|
||||||
|
| `POST` | `"/clientIps/:email"` | استرجاع عنوان IP للعميل |
|
||||||
|
| `POST` | `"/clearClientIps/:email"` | مسح عنوان IP للعميل |
|
||||||
|
| `POST` | `"/addClient"` | إضافة عميل للإدخال |
|
||||||
|
| `POST` | `"/:id/delClient/:clientId"` | حذف عميل باستخدام clientId\* |
|
||||||
|
| `POST` | `"/updateClient/:clientId"` | تحديث بيانات عميل باستخدام clientId\* |
|
||||||
|
| `POST` | `"/:id/resetClientTraffic/:email"` | إعادة ضبط ترافيك عميل |
|
||||||
|
| `POST` | `"/resetAllTraffics"` | إعادة ضبط الترافيك لكل الإدخالات |
|
||||||
|
| `POST` | `"/resetAllClientTraffics/:id"` | إعادة ضبط ترافيك كل العملاء في إدخال معين |
|
||||||
|
| `POST` | `"/delDepletedClients/:id"` | حذف العملاء المستنفدين في الإدخال (-1: الكل) |
|
||||||
|
| `POST` | `"/onlines"` | استرجاع قائمة العملاء الأونلاين (الإيميلات) |
|
||||||
|
|
||||||
|
\*- بالنسبة لحقل `clientId`:
|
||||||
|
- استخدم `client.id` لـ VMESS و VLESS.
|
||||||
|
- استخدم `client.password` لـ TROJAN.
|
||||||
|
- استخدم `client.email` لـ Shadowsocks.
|
||||||
|
|
||||||
|
- [](https://app.getpostman.com/run-collection/5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D5146551-dda3cab3-0e33-485f-96f9-d4262f437ac5%26entityType%3Dcollection%26workspaceId%3Dd64f609f-485a-4951-9b8f-876b3f917124)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## المتغيرات البيئية
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>اضغط لعرض تفاصيل المتغيرات البيئية</summary>
|
||||||
|
|
||||||
|
#### الاستخدام
|
||||||
|
|
||||||
|
| المتغير | النوع | القيمة الافتراضية |
|
||||||
|
| ---------------- | :--------------------------------------------: | ------------------: |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
|
||||||
|
|
||||||
|
مثال:
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## المعاينة
|
||||||
|
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/01-overview-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/01-overview-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/02-inbounds-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/02-inbounds-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/03-add-inbound-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/03-add-inbound-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/04-add-client-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/04-add-client-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/05-settings-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/05-settings-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/06-configs-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/06-configs-light.png">
|
||||||
|
</picture>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./media/07-bot-dark.png">
|
||||||
|
<img alt="3x-ui" src="./media/07-bot-light.png">
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
## شكر خاص لـ
|
||||||
|
|
||||||
|
- [alireza0](https://github.com/alireza0/)
|
||||||
|
|
||||||
|
## التحيات
|
||||||
|
|
||||||
|
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الرخصة: **GPL-3.0**): _قواعد محدثة لتوجيهات v2ray/xray مع تضمين دومينات إيرانية وتركيز على الأمان وحجب الإعلانات._
|
||||||
|
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الرخصة: **GPL-3.0**): _المستودع ده بيحتوي على قواعد توجيه v2ray/xray محدثة تلقائيًا بناءً على بيانات الدومينات والعناوين المحظورة في روسيا._
|
||||||
|
|
||||||
|
## عدد النجوم مع مرور الوقت
|
||||||
|
|
||||||
|
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
@@ -244,8 +244,8 @@ location /sub {
|
|||||||
|
|
||||||
## SO Recomendados
|
## SO Recomendados
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 22.04+
|
||||||
- Debian 11+
|
- Debian 12+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- OpenEuler 22.03+
|
- OpenEuler 22.03+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
@@ -253,11 +253,12 @@ location /sub {
|
|||||||
- Parch Linux
|
- Parch Linux
|
||||||
- Manjaro
|
- Manjaro
|
||||||
- Armbian
|
- Armbian
|
||||||
- AlmaLinux 8.0+
|
- AlmaLinux 9.5+
|
||||||
- Rocky Linux 8+
|
- Rocky Linux 9.5+
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
- Windows x64
|
- Windows x64
|
||||||
|
|
||||||
## Arquitecturas y Dispositivos Compatibles
|
## Arquitecturas y Dispositivos Compatibles
|
||||||
@@ -282,6 +283,7 @@ Nuestra plataforma ofrece compatibilidad con una amplia gama de arquitecturas y
|
|||||||
|
|
||||||
## Idiomas
|
## Idiomas
|
||||||
|
|
||||||
|
- Arabic (Árabe)
|
||||||
- English (inglés)
|
- English (inglés)
|
||||||
- Persian (persa)
|
- Persian (persa)
|
||||||
- Traditional Chinese (chino tradicional)
|
- Traditional Chinese (chino tradicional)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
@@ -245,8 +245,8 @@ location /sub {
|
|||||||
|
|
||||||
## سیستمعاملهای توصیه شده
|
## سیستمعاملهای توصیه شده
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 22.04+
|
||||||
- Debian 11+
|
- Debian 12+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- OpenEuler 22.03+
|
- OpenEuler 22.03+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
@@ -254,11 +254,12 @@ location /sub {
|
|||||||
- Parch Linux
|
- Parch Linux
|
||||||
- Manjaro
|
- Manjaro
|
||||||
- Armbian
|
- Armbian
|
||||||
- AlmaLinux 8.0+
|
- AlmaLinux 9.5+
|
||||||
- Rocky Linux 8+
|
- Rocky Linux 9.5+
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
- Windows x64
|
- Windows x64
|
||||||
|
|
||||||
## معماریها و دستگاههای پشتیبانی شده
|
## معماریها و دستگاههای پشتیبانی شده
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
@@ -249,8 +249,8 @@ location /sub {
|
|||||||
|
|
||||||
## Recommended OS
|
## Recommended OS
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 22.04+
|
||||||
- Debian 11+
|
- Debian 12+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- OpenEuler 22.03+
|
- OpenEuler 22.03+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
@@ -258,11 +258,12 @@ location /sub {
|
|||||||
- Parch Linux
|
- Parch Linux
|
||||||
- Manjaro
|
- Manjaro
|
||||||
- Armbian
|
- Armbian
|
||||||
- AlmaLinux 8.0+
|
- AlmaLinux 9.5+
|
||||||
- Rocky Linux 8+
|
- Rocky Linux 9.5+
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
- Windows x64
|
- Windows x64
|
||||||
|
|
||||||
## Supported Architectures and Devices
|
## Supported Architectures and Devices
|
||||||
@@ -289,6 +290,7 @@ Our platform offers compatibility with a diverse range of architectures and devi
|
|||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
- Arabic
|
||||||
- English
|
- English
|
||||||
- Persian
|
- Persian
|
||||||
- Traditional Chinese
|
- Traditional Chinese
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
@@ -248,8 +248,8 @@ location /sub {
|
|||||||
|
|
||||||
## Рекомендуемые ОС
|
## Рекомендуемые ОС
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 22.04+
|
||||||
- Debian 11+
|
- Debian 12+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- OpenEuler 22.03+
|
- OpenEuler 22.03+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
@@ -257,11 +257,12 @@ location /sub {
|
|||||||
- Parch Linux
|
- Parch Linux
|
||||||
- Manjaro
|
- Manjaro
|
||||||
- Armbian
|
- Armbian
|
||||||
- AlmaLinux 8.0+
|
- AlmaLinux 9.5+
|
||||||
- Rocky Linux 8+
|
- Rocky Linux 9.5+
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
- Windows x64
|
- Windows x64
|
||||||
|
|
||||||
## Поддерживаемые архитектуры и устройства
|
## Поддерживаемые архитектуры и устройства
|
||||||
@@ -288,6 +289,7 @@ location /sub {
|
|||||||
|
|
||||||
## Языки
|
## Языки
|
||||||
|
|
||||||
|
- Arabic (арабский)
|
||||||
- English (английский)
|
- English (английский)
|
||||||
- Persian (персидский)
|
- Persian (персидский)
|
||||||
- Traditional Chinese (традиционный китайский)
|
- Traditional Chinese (традиционный китайский)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
@@ -245,8 +245,8 @@ location /sub {
|
|||||||
|
|
||||||
## 建议使用的操作系统
|
## 建议使用的操作系统
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 22.04+
|
||||||
- Debian 11+
|
- Debian 12+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- OpenEuler 22.03+
|
- OpenEuler 22.03+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
@@ -254,11 +254,12 @@ location /sub {
|
|||||||
- Parch Linux
|
- Parch Linux
|
||||||
- Manjaro
|
- Manjaro
|
||||||
- Armbian
|
- Armbian
|
||||||
- AlmaLinux 8.0+
|
- AlmaLinux 9.5+
|
||||||
- Rocky Linux 8+
|
- Rocky Linux 9.5+
|
||||||
- Oracle Linux 8+
|
- Oracle Linux 8+
|
||||||
- OpenSUSE Tubleweed
|
- OpenSUSE Tubleweed
|
||||||
- Amazon Linux 2023
|
- Amazon Linux 2023
|
||||||
|
- Virtuozzo Linux 8+
|
||||||
- Windows x64
|
- Windows x64
|
||||||
|
|
||||||
## 支持的架构和设备
|
## 支持的架构和设备
|
||||||
@@ -282,6 +283,7 @@ location /sub {
|
|||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
- Arabic (阿拉伯)
|
||||||
- English(英语)
|
- English(英语)
|
||||||
- Persian(波斯语)
|
- Persian(波斯语)
|
||||||
- Traditional Chinese(繁体中文)
|
- Traditional Chinese(繁体中文)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.5.5
|
2.5.7
|
||||||
@@ -26,7 +26,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initModels() error {
|
func initModels() error {
|
||||||
models := []interface{}{
|
models := []any{
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Inbound{},
|
&model.Inbound{},
|
||||||
&model.OutboundTraffics{},
|
&model.OutboundTraffics{},
|
||||||
|
|||||||
67
go.mod
67
go.mod
@@ -1,45 +1,46 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.2
|
github.com/gin-contrib/gzip v1.2.3
|
||||||
github.com/gin-contrib/sessions v1.0.2
|
github.com/gin-contrib/sessions v1.0.3
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mymmrac/telego v0.32.0
|
github.com/mymmrac/telego v0.32.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1
|
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.2
|
github.com/shirou/gopsutil/v4 v4.25.3
|
||||||
github.com/valyala/fasthttp v1.59.0
|
github.com/valyala/fasthttp v1.60.0
|
||||||
github.com/xtls/xray-core v1.250306.0
|
github.com/xtls/xray-core v1.250306.1-0.20250331123338-ab5d7cf3d2d6
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.23.0
|
golang.org/x/text v0.24.0
|
||||||
google.golang.org/grpc v1.71.0
|
google.golang.org/grpc v1.71.1
|
||||||
gorm.io/driver/sqlite v1.5.7
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/bytedance/sonic v1.13.1 // indirect
|
github.com/bytedance/sonic v1.13.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudflare/circl v1.6.0 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||||
github.com/ebitengine/purego v0.8.2 // indirect
|
github.com/ebitengine/purego v0.8.2 // indirect
|
||||||
github.com/fasthttp/router v1.5.4 // indirect
|
github.com/fasthttp/router v1.5.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
@@ -50,24 +51,23 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
github.com/mattn/go-sqlite3 v1.14.27 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.23.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.50.0 // indirect
|
github.com/quic-go/quic-go v0.50.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagernet/sing v0.6.3 // indirect
|
github.com/sagernet/sing v0.6.6 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
@@ -80,22 +80,23 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
|
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.1 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.15.0 // indirect
|
golang.org/x/arch v0.16.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.37.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/net v0.37.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/sync v0.12.0 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.31.0 // indirect
|
golang.org/x/tools v0.32.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect
|
gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb // indirect
|
||||||
lukechampine.com/blake3 v1.4.0 // indirect
|
lukechampine.com/blake3 v1.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
143
go.sum
143
go.sum
@@ -1,20 +1,19 @@
|
|||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -29,12 +28,12 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
|
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
||||||
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
|
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
||||||
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
github.com/gin-contrib/sessions v1.0.3 h1:AZ4j0AalLsGqdrKNbbrKcXx9OJZqViirvNGsJTxcQps=
|
||||||
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
github.com/gin-contrib/sessions v1.0.3/go.mod h1:5i4XMx4KPtQihnzxEqG9u1K446lO3G19jAi2GtbfsAI=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
@@ -50,8 +49,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
@@ -67,8 +66,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
@@ -99,14 +98,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
|
||||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -114,28 +113,30 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
|
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
|
||||||
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||||
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
|
||||||
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
@@ -144,27 +145,25 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE=
|
github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE=
|
||||||
github.com/sagernet/sing v0.6.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk=
|
github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA=
|
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
@@ -179,8 +178,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
|||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
|
github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
|
||||||
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
@@ -190,8 +189,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
|||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||||
github.com/xtls/xray-core v1.250306.0 h1:XZyZvSgcpAoVEGnFnxNdoHbSF7Kp77A/0TPk4lhv6rM=
|
github.com/xtls/xray-core v1.250306.1-0.20250331123338-ab5d7cf3d2d6 h1:12QXC7rYztOQhq/3ooiHrEc5X98478076aUiIW8mzR8=
|
||||||
github.com/xtls/xray-core v1.250306.0/go.mod h1:clXnUOnX6CKWBGgJY4ePYhb/EtTdSrUC7vPfT6m5p4c=
|
github.com/xtls/xray-core v1.250306.1-0.20250331123338-ab5d7cf3d2d6/go.mod h1:O+FFC64bjnOukaGHPdZ+wqGHTrgPDN+qH0U+YWCzbEo=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
@@ -210,46 +209,48 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC
|
|||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
|
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||||
|
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
||||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
||||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -263,8 +264,8 @@ gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
|||||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
|
gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb h1:rOQHoZqzW4aOUPdXb3HpJmJkEUYqASpXKy4W3sUQfYE=
|
||||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
gvisor.dev/gvisor v0.0.0-20250403230555-2b1f43f26fbb/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||||
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
||||||
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
|||||||
91
install.sh
91
install.sh
@@ -37,83 +37,20 @@ arch() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "arch: $(arch)"
|
echo "Arch: $(arch)"
|
||||||
|
|
||||||
os_version=""
|
check_glibc_version() {
|
||||||
os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.')
|
glibc_version=$(ldd --version | head -n1 | awk '{print $NF}')
|
||||||
|
|
||||||
if [[ "${release}" == "arch" ]]; then
|
required_version="2.32"
|
||||||
echo "Your OS is Arch Linux"
|
if [[ "$(printf '%s\n' "$required_version" "$glibc_version" | sort -V | head -n1)" != "$required_version" ]]; then
|
||||||
elif [[ "${release}" == "parch" ]]; then
|
echo -e "${red}GLIBC version $glibc_version is too old! Required: 2.32 or higher${plain}"
|
||||||
echo "Your OS is Parch Linux"
|
echo "Please upgrade to a newer version of your operating system to get a higher GLIBC version."
|
||||||
elif [[ "${release}" == "manjaro" ]]; then
|
exit 1
|
||||||
echo "Your OS is Manjaro"
|
|
||||||
elif [[ "${release}" == "armbian" ]]; then
|
|
||||||
echo "Your OS is Armbian"
|
|
||||||
elif [[ "${release}" == "alpine" ]]; then
|
|
||||||
echo "Your OS is Alpine Linux"
|
|
||||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
|
||||||
echo "Your OS is OpenSUSE Tumbleweed"
|
|
||||||
elif [[ "${release}" == "openEuler" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2203 ]]; then
|
|
||||||
echo -e "${red} Please use OpenEuler 22.03 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
fi
|
||||||
elif [[ "${release}" == "centos" ]]; then
|
echo "GLIBC version: $glibc_version (meets requirement of 2.32+)"
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
}
|
||||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
check_glibc_version
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
|
||||||
if [[ ${os_version} -lt 2004 ]]; then
|
|
||||||
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
|
||||||
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "amzn" ]]; then
|
|
||||||
if [[ ${os_version} != "2023" ]]; then
|
|
||||||
echo -e "${red} Please use Amazon Linux 2023!${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
|
||||||
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 80 ]]; then
|
|
||||||
echo -e "${red} Please use AlmaLinux 8.0 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "rocky" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use Rocky Linux 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "ol" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
elif [[ "${release}" == "virtuozzo" ]]; then
|
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
|
||||||
echo -e "${red} Please use Virtuozzo Linux 8 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
|
||||||
echo "Please ensure you are using one of the following supported operating systems:"
|
|
||||||
echo "- Ubuntu 20.04+"
|
|
||||||
echo "- Debian 11+"
|
|
||||||
echo "- CentOS 8+"
|
|
||||||
echo "- OpenEuler 22.03+"
|
|
||||||
echo "- Fedora 36+"
|
|
||||||
echo "- Arch Linux"
|
|
||||||
echo "- Parch Linux"
|
|
||||||
echo "- Manjaro"
|
|
||||||
echo "- Armbian"
|
|
||||||
echo "- AlmaLinux 8.0+"
|
|
||||||
echo "- Rocky Linux 8+"
|
|
||||||
echo "- Oracle Linux 8+"
|
|
||||||
echo "- OpenSUSE Tumbleweed"
|
|
||||||
echo "- Amazon Linux 2023"
|
|
||||||
echo "- Virtuozzo Linux 8+"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
@@ -157,9 +94,9 @@ config_after_install() {
|
|||||||
local config_username=$(gen_random_string 10)
|
local config_username=$(gen_random_string 10)
|
||||||
local config_password=$(gen_random_string 10)
|
local config_password=$(gen_random_string 10)
|
||||||
|
|
||||||
read -p "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
||||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||||
read -p "Please set up the panel port: " config_port
|
read -rp "Please set up the panel port: " config_port
|
||||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||||
else
|
else
|
||||||
local config_port=$(shuf -i 1024-62000 -n 1)
|
local config_port=$(shuf -i 1024-62000 -n 1)
|
||||||
|
|||||||
@@ -47,52 +47,52 @@ func InitLogger(level logging.Level) {
|
|||||||
logger = newLogger
|
logger = newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...any) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...any) {
|
||||||
logger.Debugf(format, args...)
|
logger.Debugf(format, args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(args ...interface{}) {
|
func Info(args ...any) {
|
||||||
logger.Info(args...)
|
logger.Info(args...)
|
||||||
addToBuffer("INFO", fmt.Sprint(args...))
|
addToBuffer("INFO", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...any) {
|
||||||
logger.Infof(format, args...)
|
logger.Infof(format, args...)
|
||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Notice(args ...interface{}) {
|
func Notice(args ...any) {
|
||||||
logger.Notice(args...)
|
logger.Notice(args...)
|
||||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Noticef(format string, args ...interface{}) {
|
func Noticef(format string, args ...any) {
|
||||||
logger.Noticef(format, args...)
|
logger.Noticef(format, args...)
|
||||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...any) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...any) {
|
||||||
logger.Warningf(format, args...)
|
logger.Warningf(format, args...)
|
||||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(args ...interface{}) {
|
func Error(args ...any) {
|
||||||
logger.Error(args...)
|
logger.Error(args...)
|
||||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...any) {
|
||||||
logger.Errorf(format, args...)
|
logger.Errorf(format, args...)
|
||||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,11 +107,16 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
SubJsonRules = ""
|
SubJsonRules = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubTitle, err := s.settingService.GetSubTitle()
|
||||||
|
if err != nil {
|
||||||
|
SubTitle = ""
|
||||||
|
}
|
||||||
|
|
||||||
g := engine.Group("/")
|
g := engine.Group("/")
|
||||||
|
|
||||||
s.sub = NewSUBController(
|
s.sub = NewSUBController(
|
||||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules)
|
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
|
subTitle string
|
||||||
subPath string
|
subPath string
|
||||||
subJsonPath string
|
subJsonPath string
|
||||||
subEncrypt bool
|
subEncrypt bool
|
||||||
@@ -30,9 +31,11 @@ func NewSUBController(
|
|||||||
jsonNoise string,
|
jsonNoise string,
|
||||||
jsonMux string,
|
jsonMux string,
|
||||||
jsonRules string,
|
jsonRules string,
|
||||||
|
subTitle string,
|
||||||
) *SUBController {
|
) *SUBController {
|
||||||
sub := NewSubService(showInfo, rModel)
|
sub := NewSubService(showInfo, rModel)
|
||||||
a := &SUBController{
|
a := &SUBController{
|
||||||
|
subTitle: subTitle,
|
||||||
subPath: subPath,
|
subPath: subPath,
|
||||||
subJsonPath: jsonPath,
|
subJsonPath: jsonPath,
|
||||||
subEncrypt: encrypt,
|
subEncrypt: encrypt,
|
||||||
@@ -82,7 +85,7 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", subId)
|
c.Writer.Header().Set("Profile-Title", a.subTitle)
|
||||||
|
|
||||||
if a.subEncrypt {
|
if a.subEncrypt {
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
@@ -116,7 +119,7 @@ func (a *SUBController) subJsons(c *gin.Context) {
|
|||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", subId)
|
c.Writer.Header().Set("Profile-Title", a.subTitle)
|
||||||
|
|
||||||
c.String(200, jsonSub)
|
c.String(200, jsonSub)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
var defaultJson string
|
var defaultJson string
|
||||||
|
|
||||||
type SubJsonService struct {
|
type SubJsonService struct {
|
||||||
configJson map[string]interface{}
|
configJson map[string]any
|
||||||
defaultOutbounds []json_util.RawMessage
|
defaultOutbounds []json_util.RawMessage
|
||||||
fragment string
|
fragment string
|
||||||
noises string
|
noises string
|
||||||
@@ -29,10 +29,10 @@ type SubJsonService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
|
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||||
var configJson map[string]interface{}
|
var configJson map[string]any
|
||||||
var defaultOutbounds []json_util.RawMessage
|
var defaultOutbounds []json_util.RawMessage
|
||||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||||
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
if outboundSlices, ok := configJson["outbounds"].([]any); ok {
|
||||||
for _, defaultOutbound := range outboundSlices {
|
for _, defaultOutbound := range outboundSlices {
|
||||||
jsonBytes, _ := json.Marshal(defaultOutbound)
|
jsonBytes, _ := json.Marshal(defaultOutbound)
|
||||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||||
@@ -40,9 +40,9 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rules != "" {
|
if rules != "" {
|
||||||
var newRules []interface{}
|
var newRules []any
|
||||||
routing, _ := configJson["routing"].(map[string]interface{})
|
routing, _ := configJson["routing"].(map[string]any)
|
||||||
defaultRules, _ := routing["rules"].([]interface{})
|
defaultRules, _ := routing["rules"].([]any)
|
||||||
json.Unmarshal([]byte(rules), &newRules)
|
json.Unmarshal([]byte(rules), &newRules)
|
||||||
defaultRules = append(newRules, defaultRules...)
|
defaultRules = append(newRules, defaultRules...)
|
||||||
routing["rules"] = defaultRules
|
routing["rules"] = defaultRules
|
||||||
@@ -148,10 +148,10 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
var newJsonArray []json_util.RawMessage
|
var newJsonArray []json_util.RawMessage
|
||||||
stream := s.streamData(inbound.StreamSettings)
|
stream := s.streamData(inbound.StreamSettings)
|
||||||
|
|
||||||
externalProxies, ok := stream["externalProxy"].([]interface{})
|
externalProxies, ok := stream["externalProxy"].([]any)
|
||||||
if !ok || len(externalProxies) == 0 {
|
if !ok || len(externalProxies) == 0 {
|
||||||
externalProxies = []interface{}{
|
externalProxies = []any{
|
||||||
map[string]interface{}{
|
map[string]any{
|
||||||
"forceTls": "same",
|
"forceTls": "same",
|
||||||
"dest": host,
|
"dest": host,
|
||||||
"port": float64(inbound.Port),
|
"port": float64(inbound.Port),
|
||||||
@@ -163,7 +163,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
delete(stream, "externalProxy")
|
delete(stream, "externalProxy")
|
||||||
|
|
||||||
for _, ep := range externalProxies {
|
for _, ep := range externalProxies {
|
||||||
extPrxy := ep.(map[string]interface{})
|
extPrxy := ep.(map[string]any)
|
||||||
inbound.Listen = extPrxy["dest"].(string)
|
inbound.Listen = extPrxy["dest"].(string)
|
||||||
inbound.Port = int(extPrxy["port"].(float64))
|
inbound.Port = int(extPrxy["port"].(float64))
|
||||||
newStream := stream
|
newStream := stream
|
||||||
@@ -171,7 +171,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
case "tls":
|
case "tls":
|
||||||
if newStream["security"] != "tls" {
|
if newStream["security"] != "tls" {
|
||||||
newStream["security"] = "tls"
|
newStream["security"] = "tls"
|
||||||
newStream["tslSettings"] = map[string]interface{}{}
|
newStream["tslSettings"] = map[string]any{}
|
||||||
}
|
}
|
||||||
case "none":
|
case "none":
|
||||||
if newStream["security"] != "none" {
|
if newStream["security"] != "none" {
|
||||||
@@ -191,7 +191,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
}
|
}
|
||||||
|
|
||||||
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
||||||
newConfigJson := make(map[string]interface{})
|
newConfigJson := make(map[string]any)
|
||||||
for key, value := range s.configJson {
|
for key, value := range s.configJson {
|
||||||
newConfigJson[key] = value
|
newConfigJson[key] = value
|
||||||
}
|
}
|
||||||
@@ -205,14 +205,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
return newJsonArray
|
return newJsonArray
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
func (s *SubJsonService) streamData(stream string) map[string]any {
|
||||||
var streamSettings map[string]interface{}
|
var streamSettings map[string]any
|
||||||
json.Unmarshal([]byte(stream), &streamSettings)
|
json.Unmarshal([]byte(stream), &streamSettings)
|
||||||
security, _ := streamSettings["security"].(string)
|
security, _ := streamSettings["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
|
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
||||||
} else if security == "reality" {
|
} else if security == "reality" {
|
||||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
|
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
||||||
}
|
}
|
||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
@@ -233,17 +233,17 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
|||||||
return streamSettings
|
return streamSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
|
func (s *SubJsonService) removeAcceptProxy(setting any) map[string]any {
|
||||||
netSettings, ok := setting.(map[string]interface{})
|
netSettings, ok := setting.(map[string]any)
|
||||||
if ok {
|
if ok {
|
||||||
delete(netSettings, "acceptProxyProtocol")
|
delete(netSettings, "acceptProxyProtocol")
|
||||||
}
|
}
|
||||||
return netSettings
|
return netSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
|
||||||
tlsData := make(map[string]interface{}, 1)
|
tlsData := make(map[string]any, 1)
|
||||||
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
|
tlsClientSettings, _ := tData["settings"].(map[string]any)
|
||||||
|
|
||||||
tlsData["serverName"] = tData["serverName"]
|
tlsData["serverName"] = tData["serverName"]
|
||||||
tlsData["alpn"] = tData["alpn"]
|
tlsData["alpn"] = tData["alpn"]
|
||||||
@@ -256,9 +256,9 @@ func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interf
|
|||||||
return tlsData
|
return tlsData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
|
||||||
rltyData := make(map[string]interface{}, 1)
|
rltyData := make(map[string]any, 1)
|
||||||
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
|
rltyClientSettings, _ := rData["settings"].(map[string]any)
|
||||||
|
|
||||||
rltyData["show"] = false
|
rltyData["show"] = false
|
||||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||||
@@ -266,13 +266,13 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
|||||||
|
|
||||||
// Set random data
|
// Set random data
|
||||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||||
shortIds, ok := rData["shortIds"].([]interface{})
|
shortIds, ok := rData["shortIds"].([]any)
|
||||||
if ok && len(shortIds) > 0 {
|
if ok && len(shortIds) > 0 {
|
||||||
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
} else {
|
} else {
|
||||||
rltyData["shortId"] = ""
|
rltyData["shortId"] = ""
|
||||||
}
|
}
|
||||||
serverNames, ok := rData["serverNames"].([]interface{})
|
serverNames, ok := rData["serverNames"].([]any)
|
||||||
if ok && len(serverNames) > 0 {
|
if ok && len(serverNames) > 0 {
|
||||||
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
||||||
} else {
|
} else {
|
||||||
@@ -329,7 +329,7 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
}
|
}
|
||||||
|
|
||||||
if inbound.Protocol == model.Shadowsocks {
|
if inbound.Protocol == model.Shadowsocks {
|
||||||
var inboundSettings map[string]interface{}
|
var inboundSettings map[string]any
|
||||||
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
||||||
method, _ := inboundSettings["method"].(string)
|
method, _ := inboundSettings["method"].(string)
|
||||||
serverData[0].Method = method
|
serverData[0].Method = method
|
||||||
@@ -357,12 +357,12 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
ProxySettings map[string]any `json:"proxySettings,omitempty"`
|
||||||
Settings OutboundSettings `json:"settings,omitempty"`
|
Settings OutboundSettings `json:"settings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundSettings struct {
|
type OutboundSettings struct {
|
||||||
|
|||||||
@@ -141,9 +141,9 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
|||||||
return "", 0, "", err
|
return "", 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(streamSettings), &stream)
|
json.Unmarshal([]byte(streamSettings), &stream)
|
||||||
var masterStream map[string]interface{}
|
var masterStream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
||||||
stream["security"] = masterStream["security"]
|
stream["security"] = masterStream["security"]
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
@@ -171,66 +171,66 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VMESS {
|
if inbound.Protocol != model.VMESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
obj := map[string]interface{}{
|
obj := map[string]any{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"add": s.address,
|
"add": s.address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
network, _ := stream["network"].(string)
|
network, _ := stream["network"].(string)
|
||||||
obj["net"] = network
|
obj["net"] = network
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||||
header, _ := tcp["header"].(map[string]interface{})
|
header, _ := tcp["header"].(map[string]any)
|
||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
obj["type"] = typeStr
|
obj["type"] = typeStr
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]interface{})
|
request := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]interface{})
|
requestPath, _ := request["path"].([]any)
|
||||||
obj["path"] = requestPath[0].(string)
|
obj["path"] = requestPath[0].(string)
|
||||||
headers, _ := request["headers"].(map[string]interface{})
|
headers, _ := request["headers"].(map[string]any)
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||||
header, _ := kcp["header"].(map[string]interface{})
|
header, _ := kcp["header"].(map[string]any)
|
||||||
obj["type"], _ = header["type"].(string)
|
obj["type"], _ = header["type"].(string)
|
||||||
obj["path"], _ = kcp["seed"].(string)
|
obj["path"], _ = kcp["seed"].(string)
|
||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]any)
|
||||||
obj["path"] = ws["path"].(string)
|
obj["path"] = ws["path"].(string)
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||||
obj["host"] = host
|
obj["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]any)
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
obj["path"] = grpc["serviceName"].(string)
|
||||||
obj["authority"] = grpc["authority"].(string)
|
obj["authority"] = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
obj["type"] = "multi"
|
obj["type"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||||
obj["path"] = httpupgrade["path"].(string)
|
obj["path"] = httpupgrade["path"].(string)
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||||
obj["host"] = host
|
obj["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "xhttp":
|
case "xhttp":
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||||
obj["path"] = xhttp["path"].(string)
|
obj["path"] = xhttp["path"].(string)
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
obj["host"] = host
|
obj["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]any)
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
obj["mode"] = xhttp["mode"].(string)
|
obj["mode"] = xhttp["mode"].(string)
|
||||||
@@ -238,8 +238,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
alpns, _ := tlsSetting["alpn"].([]any)
|
||||||
if len(alpns) > 0 {
|
if len(alpns) > 0 {
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
@@ -273,14 +273,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
obj["scy"] = clients[clientIndex].Security
|
obj["scy"] = clients[clientIndex].Security
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, externalProxy := range externalProxies {
|
for index, externalProxy := range externalProxies {
|
||||||
ep, _ := externalProxy.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]any)
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
newObj := map[string]interface{}{}
|
newObj := map[string]any{}
|
||||||
for key, value := range obj {
|
for key, value := range obj {
|
||||||
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
||||||
newObj[key] = value
|
newObj[key] = value
|
||||||
@@ -313,7 +313,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
@@ -331,54 +331,54 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
|
|
||||||
switch streamNetwork {
|
switch streamNetwork {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||||
header, _ := tcp["header"].(map[string]interface{})
|
header, _ := tcp["header"].(map[string]any)
|
||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]interface{})
|
request := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]interface{})
|
requestPath, _ := request["path"].([]any)
|
||||||
params["path"] = requestPath[0].(string)
|
params["path"] = requestPath[0].(string)
|
||||||
headers, _ := request["headers"].(map[string]interface{})
|
headers, _ := request["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
params["headerType"] = "http"
|
params["headerType"] = "http"
|
||||||
}
|
}
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||||
header, _ := kcp["header"].(map[string]interface{})
|
header, _ := kcp["header"].(map[string]any)
|
||||||
params["headerType"] = header["type"].(string)
|
params["headerType"] = header["type"].(string)
|
||||||
params["seed"] = kcp["seed"].(string)
|
params["seed"] = kcp["seed"].(string)
|
||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]any)
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
params["authority"], _ = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "xhttp":
|
case "xhttp":
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||||
params["path"] = xhttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"] = xhttp["mode"].(string)
|
params["mode"] = xhttp["mode"].(string)
|
||||||
@@ -386,8 +386,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
alpns, _ := tlsSetting["alpn"].([]any)
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
alpn = append(alpn, a.(string))
|
alpn = append(alpn, a.(string))
|
||||||
@@ -418,18 +418,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
params["security"] = "reality"
|
params["security"] = "reality"
|
||||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
||||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||||
if realitySetting != nil {
|
if realitySetting != nil {
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]any)
|
||||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]any)
|
||||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
@@ -449,12 +449,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, externalProxy := range externalProxies {
|
for index, externalProxy := range externalProxies {
|
||||||
ep, _ := externalProxy.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]any)
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
dest, _ := ep["dest"].(string)
|
dest, _ := ep["dest"].(string)
|
||||||
port := int(ep["port"].(float64))
|
port := int(ep["port"].(float64))
|
||||||
@@ -507,7 +507,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if inbound.Protocol != model.Trojan {
|
if inbound.Protocol != model.Trojan {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
@@ -525,54 +525,54 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
|
|
||||||
switch streamNetwork {
|
switch streamNetwork {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||||
header, _ := tcp["header"].(map[string]interface{})
|
header, _ := tcp["header"].(map[string]any)
|
||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]interface{})
|
request := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]interface{})
|
requestPath, _ := request["path"].([]any)
|
||||||
params["path"] = requestPath[0].(string)
|
params["path"] = requestPath[0].(string)
|
||||||
headers, _ := request["headers"].(map[string]interface{})
|
headers, _ := request["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
params["headerType"] = "http"
|
params["headerType"] = "http"
|
||||||
}
|
}
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||||
header, _ := kcp["header"].(map[string]interface{})
|
header, _ := kcp["header"].(map[string]any)
|
||||||
params["headerType"] = header["type"].(string)
|
params["headerType"] = header["type"].(string)
|
||||||
params["seed"] = kcp["seed"].(string)
|
params["seed"] = kcp["seed"].(string)
|
||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]any)
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
params["authority"], _ = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "xhttp":
|
case "xhttp":
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||||
params["path"] = xhttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"] = xhttp["mode"].(string)
|
params["mode"] = xhttp["mode"].(string)
|
||||||
@@ -580,8 +580,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
alpns, _ := tlsSetting["alpn"].([]any)
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
alpn = append(alpn, a.(string))
|
alpn = append(alpn, a.(string))
|
||||||
@@ -608,18 +608,18 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
params["security"] = "reality"
|
params["security"] = "reality"
|
||||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
||||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||||
if realitySetting != nil {
|
if realitySetting != nil {
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]interface{})
|
sNames, _ := sniValue.([]any)
|
||||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]any)
|
||||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
@@ -639,12 +639,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, externalProxy := range externalProxies {
|
for index, externalProxy := range externalProxies {
|
||||||
ep, _ := externalProxy.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]any)
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
dest, _ := ep["dest"].(string)
|
dest, _ := ep["dest"].(string)
|
||||||
port := int(ep["port"].(float64))
|
port := int(ep["port"].(float64))
|
||||||
@@ -698,11 +698,11 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
if inbound.Protocol != model.Shadowsocks {
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
inboundPassword := settings["password"].(string)
|
inboundPassword := settings["password"].(string)
|
||||||
method := settings["method"].(string)
|
method := settings["method"].(string)
|
||||||
@@ -719,54 +719,54 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
|
|
||||||
switch streamNetwork {
|
switch streamNetwork {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||||
header, _ := tcp["header"].(map[string]interface{})
|
header, _ := tcp["header"].(map[string]any)
|
||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]interface{})
|
request := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]interface{})
|
requestPath, _ := request["path"].([]any)
|
||||||
params["path"] = requestPath[0].(string)
|
params["path"] = requestPath[0].(string)
|
||||||
headers, _ := request["headers"].(map[string]interface{})
|
headers, _ := request["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
params["headerType"] = "http"
|
params["headerType"] = "http"
|
||||||
}
|
}
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||||
header, _ := kcp["header"].(map[string]interface{})
|
header, _ := kcp["header"].(map[string]any)
|
||||||
params["headerType"] = header["type"].(string)
|
params["headerType"] = header["type"].(string)
|
||||||
params["seed"] = kcp["seed"].(string)
|
params["seed"] = kcp["seed"].(string)
|
||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]any)
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
params["authority"], _ = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if grpc["multiMode"].(bool) {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "xhttp":
|
case "xhttp":
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||||
params["path"] = xhttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]any)
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
params["mode"] = xhttp["mode"].(string)
|
params["mode"] = xhttp["mode"].(string)
|
||||||
@@ -775,8 +775,8 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
alpns, _ := tlsSetting["alpn"].([]any)
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
alpn = append(alpn, a.(string))
|
alpn = append(alpn, a.(string))
|
||||||
@@ -806,12 +806,12 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
externalProxies, _ := stream["externalProxy"].([]any)
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, externalProxy := range externalProxies {
|
for index, externalProxy := range externalProxies {
|
||||||
ep, _ := externalProxy.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]any)
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
dest, _ := ep["dest"].(string)
|
dest, _ := ep["dest"].(string)
|
||||||
port := int(ep["port"].(float64))
|
port := int(ep["port"].(float64))
|
||||||
@@ -944,9 +944,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
|||||||
return strings.Join(remark, separationChar)
|
return strings.Join(remark, separationChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data any, key string) (any, bool) {
|
||||||
switch val := data.(type) {
|
switch val := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
for k, v := range val {
|
for k, v := range val {
|
||||||
if k == key {
|
if k == key {
|
||||||
return v, true
|
return v, true
|
||||||
@@ -955,7 +955,7 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
|
|||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, v := range val {
|
for _, v := range val {
|
||||||
if result, ok := searchKey(v, key); ok {
|
if result, ok := searchKey(v, key); ok {
|
||||||
return result, true
|
return result, true
|
||||||
@@ -965,19 +965,19 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchHost(headers interface{}) string {
|
func searchHost(headers any) string {
|
||||||
data, _ := headers.(map[string]interface{})
|
data, _ := headers.(map[string]any)
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
if strings.EqualFold(k, "host") {
|
if strings.EqualFold(k, "host") {
|
||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
hosts, _ := v.([]interface{})
|
hosts, _ := v.([]any)
|
||||||
if len(hosts) > 0 {
|
if len(hosts) > 0 {
|
||||||
return hosts[0].(string)
|
return hosts[0].(string)
|
||||||
} else {
|
} else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
case interface{}:
|
case any:
|
||||||
return v.(string)
|
return v.(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ import (
|
|||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewErrorf(format string, a ...interface{}) error {
|
func NewErrorf(format string, a ...any) error {
|
||||||
msg := fmt.Sprintf(format, a...)
|
msg := fmt.Sprintf(format, a...)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewError(a ...interface{}) error {
|
func NewError(a ...any) error {
|
||||||
msg := fmt.Sprintln(a...)
|
msg := fmt.Sprintln(a...)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Recover(msg string) interface{} {
|
func Recover(msg string) any {
|
||||||
panicErr := recover()
|
panicErr := recover()
|
||||||
if panicErr != nil {
|
if panicErr != nil {
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
|
|||||||
3
web/assets/axios/axios.min.js
vendored
3
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}
|
|
||||||
@@ -26,6 +26,7 @@ class AllSetting {
|
|||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
this.secretEnable = false;
|
this.secretEnable = false;
|
||||||
this.subEnable = false;
|
this.subEnable = false;
|
||||||
|
this.subTitle = "";
|
||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = 2096;
|
this.subPort = 2096;
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class PromiseUtil {
|
|||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
|
static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
|
||||||
let seq = '';
|
let seq = '';
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "hex":
|
case "hex":
|
||||||
seq += "0123456789abcdef";
|
seq += "0123456789abcdef";
|
||||||
@@ -488,7 +488,7 @@ class ClipboardManager {
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
try {
|
try {
|
||||||
const textarea = window.document.createElement('textarea');
|
const textarea = window.document.createElement('textarea');
|
||||||
|
|
||||||
textarea.style.fontSize = '12pt';
|
textarea.style.fontSize = '12pt';
|
||||||
textarea.style.border = '0';
|
textarea.style.border = '0';
|
||||||
textarea.style.padding = '0';
|
textarea.style.padding = '0';
|
||||||
@@ -498,14 +498,14 @@ class ClipboardManager {
|
|||||||
textarea.style.top = `${window.pageYOffset || document.documentElement.scrollTop}px`;
|
textarea.style.top = `${window.pageYOffset || document.documentElement.scrollTop}px`;
|
||||||
textarea.setAttribute('readonly', '');
|
textarea.setAttribute('readonly', '');
|
||||||
textarea.value = content;
|
textarea.value = content;
|
||||||
|
|
||||||
window.document.body.appendChild(textarea);
|
window.document.body.appendChild(textarea);
|
||||||
|
|
||||||
textarea.select();
|
textarea.select();
|
||||||
window.document.execCommand("copy");
|
window.document.execCommand("copy");
|
||||||
|
|
||||||
window.document.body.removeChild(textarea);
|
window.document.body.removeChild(textarea);
|
||||||
|
|
||||||
resolve(true)
|
resolve(true)
|
||||||
} catch {
|
} catch {
|
||||||
resolve(false)
|
resolve(false)
|
||||||
@@ -558,7 +558,7 @@ class CPUFormatter {
|
|||||||
static cpuSpeedFormat(speed) {
|
static cpuSpeedFormat(speed) {
|
||||||
return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz";
|
return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz";
|
||||||
}
|
}
|
||||||
|
|
||||||
static cpuCoreFormat(cores) {
|
static cpuCoreFormat(cores) {
|
||||||
return cores === 1 ? "1 Core" : cores + " Cores";
|
return cores === 1 ? "1 Core" : cores + " Cores";
|
||||||
}
|
}
|
||||||
@@ -579,7 +579,7 @@ class NumberFormatter {
|
|||||||
static addZero(num) {
|
static addZero(num) {
|
||||||
return num < 10 ? "0" + num : num;
|
return num < 10 ? "0" + num : num;
|
||||||
}
|
}
|
||||||
|
|
||||||
static toFixed(num, n) {
|
static toFixed(num, n) {
|
||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.floor(num * n) / n;
|
return Math.floor(num * n) / n;
|
||||||
@@ -610,7 +610,7 @@ class CookieManager {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
static setCookie(cname, cvalue, exdays) {
|
static setCookie(cname, cvalue, exdays) {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||||
@@ -630,7 +630,7 @@ class ColorUtils {
|
|||||||
default: return "red";
|
default: return "red";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static clientUsageColor(clientStats, trafficDiff) {
|
static clientUsageColor(clientStats, trafficDiff) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case !clientStats || clientStats.total == 0: return "#7a316f";
|
case !clientStats || clientStats.total == 0: return "#7a316f";
|
||||||
@@ -639,7 +639,7 @@ class ColorUtils {
|
|||||||
default: return "#cf3c3c";
|
default: return "#cf3c3c";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static userExpiryColor(threshold, client, isDark = false) {
|
static userExpiryColor(threshold, client, isDark = false) {
|
||||||
if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc';
|
if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc';
|
||||||
let now = new Date().getTime(), expiry = client.expiryTime;
|
let now = new Date().getTime(), expiry = client.expiryTime;
|
||||||
@@ -665,7 +665,7 @@ class URLBuilder {
|
|||||||
if (!host || host.length === 0) host = window.location.hostname;
|
if (!host || host.length === 0) host = window.location.hostname;
|
||||||
if (!port || port.length === 0) port = window.location.port;
|
if (!port || port.length === 0) port = window.location.port;
|
||||||
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||||
|
|
||||||
const protocol = isTLS ? "https:" : "http:";
|
const protocol = isTLS ? "https:" : "http:";
|
||||||
port = String(port);
|
port = String(port);
|
||||||
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||||
@@ -673,13 +673,18 @@ class URLBuilder {
|
|||||||
} else {
|
} else {
|
||||||
port = `:${port}`;
|
port = `:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${protocol}//${host}${port}${base}${path}`;
|
return `${protocol}//${host}${port}${base}${path}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LanguageManager {
|
class LanguageManager {
|
||||||
static supportedLanguages = [
|
static supportedLanguages = [
|
||||||
|
{
|
||||||
|
name: "العربية",
|
||||||
|
value: "ar-EG",
|
||||||
|
icon: "🇪🇬",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "English",
|
name: "English",
|
||||||
value: "en-US",
|
value: "en-US",
|
||||||
@@ -744,11 +749,11 @@ class LanguageManager {
|
|||||||
|
|
||||||
static getLanguage() {
|
static getLanguage() {
|
||||||
let lang = CookieManager.getCookie("lang");
|
let lang = CookieManager.getCookie("lang");
|
||||||
|
|
||||||
if (!lang) {
|
if (!lang) {
|
||||||
if (window.navigator) {
|
if (window.navigator) {
|
||||||
lang = window.navigator.language || window.navigator.userLanguage;
|
lang = window.navigator.language || window.navigator.userLanguage;
|
||||||
|
|
||||||
if (LanguageManager.isSupportLanguage(lang)) {
|
if (LanguageManager.isSupportLanguage(lang)) {
|
||||||
CookieManager.setCookie("lang", lang, 150);
|
CookieManager.setCookie("lang", lang, 150);
|
||||||
} else {
|
} else {
|
||||||
@@ -760,24 +765,63 @@ class LanguageManager {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lang;
|
return lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setLanguage(language) {
|
static setLanguage(language) {
|
||||||
if (!LanguageManager.isSupportLanguage(language)) {
|
if (!LanguageManager.isSupportLanguage(language)) {
|
||||||
language = "en-US";
|
language = "en-US";
|
||||||
}
|
}
|
||||||
|
|
||||||
CookieManager.setCookie("lang", language, 150);
|
CookieManager.setCookie("lang", language, 150);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
static isSupportLanguage(language) {
|
static isSupportLanguage(language) {
|
||||||
const languageFilter = LanguageManager.supportedLanguages.filter((lang) => {
|
const languageFilter = LanguageManager.supportedLanguages.filter((lang) => {
|
||||||
return lang.value === language
|
return lang.value === language
|
||||||
})
|
})
|
||||||
|
|
||||||
return languageFilter.length > 0;
|
return languageFilter.length > 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MediaQueryMixin = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateDeviceType() {
|
||||||
|
this.isMobile = window.innerWidth <= 768;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener('resize', this.updateDeviceType);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.updateDeviceType);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileManager {
|
||||||
|
static downloadTextFile(content, filename='file.txt', options = { type: "text/plain" }) {
|
||||||
|
let link = window.document.createElement('a');
|
||||||
|
|
||||||
|
link.download = filename;
|
||||||
|
link.style.border = '0';
|
||||||
|
link.style.padding = '0';
|
||||||
|
link.style.margin = '0';
|
||||||
|
link.style.position = 'absolute';
|
||||||
|
link.style.left = '-9999px';
|
||||||
|
link.style.top = `${window.pageYOffset || window.document.documentElement.scrollTop}px`;
|
||||||
|
link.href = URL.createObjectURL(new Blob([content], options));
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,11 +31,11 @@ func jsonMsg(c *gin.Context, msg string, err error) {
|
|||||||
jsonMsgObj(c, msg, nil, err)
|
jsonMsgObj(c, msg, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonObj(c *gin.Context, obj interface{}, err error) {
|
func jsonObj(c *gin.Context, obj any, err error) {
|
||||||
jsonMsgObj(c, "", obj, err)
|
jsonMsgObj(c, "", obj, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
func jsonMsgObj(c *gin.Context, msg string, obj any, err error) {
|
||||||
m := entity.Msg{
|
m := entity.Msg{
|
||||||
Obj: obj,
|
Obj: obj,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Obj interface{} `json:"obj"`
|
Obj any `json:"obj"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
@@ -40,6 +40,7 @@ type AllSetting struct {
|
|||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
|
SubTitle string `json:"subTitle" form:"subTitle"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue/antd.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui/theme-chalk/display.css">
|
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.min.css?{{ .cur_ver }}">
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
'Segoe UI Emoji', 'Segoe UI Symbol';
|
'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }} – {{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
<div id="message"></div>
|
<div id="message"></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
{{define "client_table"}}
|
{{define "component/aClientTable"}}
|
||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon style="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '24px' }" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon style="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '24px' }" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
<a-icon :style="{ fontSize: '24px' }" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :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: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
|
<a-icon slot="icon" type="question-circle-o" :style="{ color: 'var(--color-primary-100)'}"></a-icon>
|
||||||
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
<a-icon :style="{ fontSize: '24px', cursor: 'pointer' }" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-popconfirm @confirm="delClient(record.id,client,false)" title='{{ i18n "pages.inbounds.deleteClientContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "delete"}}' ok-type="danger" cancel-text='{{ i18n "cancel"}}'>
|
<a-popconfirm @confirm="delClient(record.id,client,false)" title='{{ i18n "pages.inbounds.deleteClientContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "delete"}}' ok-type="danger" cancel-text='{{ i18n "cancel"}}'>
|
||||||
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
|
<a-icon slot="icon" type="question-circle-o" :style="{ color: '#e04141' }"></a-icon>
|
||||||
<a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
<a-icon :style="{ fontSize: '24px', cursor: 'pointer' }" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@@ -124,9 +124,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
<a-tag :style="{ minWidth: '50px', border: 'none' }" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
|
<a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" :style="{ border: 'none' }" class="infinite-tag">
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -135,27 +135,27 @@
|
|||||||
</template>
|
</template>
|
||||||
<template slot="actionMenu" slot-scope="text, client, index">
|
<template slot="actionMenu" slot-scope="text, client, index">
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="ellipsis" :style="{ fontSize: '20px' }"></a-icon>
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
||||||
<a-icon style="font-size: 14px;" type="qrcode"></a-icon>
|
<a-icon :style="{ fontSize: '14px' }" type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item @click="openEditClient(record.id,client);">
|
<a-menu-item @click="openEditClient(record.id,client);">
|
||||||
<a-icon style="font-size: 14px;" type="edit"></a-icon>
|
<a-icon :style="{ fontSize: '14px' }" type="edit"></a-icon>
|
||||||
{{ i18n "pages.client.edit" }}
|
{{ i18n "pages.client.edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item @click="showInfo(record.id,client);">
|
<a-menu-item @click="showInfo(record.id,client);">
|
||||||
<a-icon style="font-size: 14px;" type="info-circle"></a-icon>
|
<a-icon :style="{ fontSize: '14px' }" type="info-circle"></a-icon>
|
||||||
{{ i18n "info" }}
|
{{ i18n "info" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
||||||
<a-icon style="font-size: 14px;" type="retweet"></a-icon>
|
<a-icon :style="{ fontSize: '14px' }" type="retweet"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetTraffic" }}
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
||||||
<a-icon style="font-size: 14px;" type="delete"></a-icon>
|
<a-icon :style="{ fontSize: '14px' }" type="delete"></a-icon>
|
||||||
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
<span :style="{ color: '#FF4D4F' }"> {{ i18n "delete"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item>
|
<a-menu-item>
|
||||||
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
@@ -169,10 +169,10 @@
|
|||||||
<template slot="content">
|
<template slot="content">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
<td colspan="3" :style="{ textAlign: 'center' }">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[ SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
||||||
<td width="120px" v-if="!client.enable">
|
<td width="120px" v-if="!client.enable">
|
||||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
||||||
</td>
|
</td>
|
||||||
@@ -202,14 +202,14 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" style="text-align: center;">
|
<td colspan="3" :style="{ textAlign: 'center' }">
|
||||||
<a-divider style="margin: 0; border-collapse: separate;"></a-divider>
|
<a-divider :style="{ margin: '0', borderCollapse: 'separate' }"></a-divider>
|
||||||
{{ i18n "pages.inbounds.expireDate" }}
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<template v-if="client.expiryTime !=0 && client.reset >0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ remainedDays(client.expiryTime) ]] </td>
|
<td width="80px" :style="{ margin: '0', textAlign: 'right', fontSize: '1em' }"> [[ remainedDays(client.expiryTime) ]] </td>
|
||||||
<td width="120px" class="infinite-bar">
|
<td width="120px" class="infinite-bar">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
<td width="60px">[[ client.reset + "d" ]]</td>
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<td colspan="3" style="text-align: center;">
|
<td colspan="3" :style="{ textAlign: 'center' }">
|
||||||
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
<a-tag :style="{ minWidth: '50px', border: 'none' }" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
@@ -257,8 +257,8 @@
|
|||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-badge>
|
<a-badge>
|
||||||
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
<a-icon v-if="!client.enable" slot="count" type="pause-circle" theme="filled" :style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon>
|
||||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
||||||
<a-icon type="solution"></a-icon>
|
<a-icon type="solution"></a-icon>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-badge>
|
</a-badge>
|
||||||
42
web/html/component/aCustomStatistic.html
Normal file
42
web/html/component/aCustomStatistic.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{{define "component/customStatistic"}}
|
||||||
|
<template>
|
||||||
|
<a-statistic :title="title" :value="value">
|
||||||
|
<template #prefix>
|
||||||
|
<slot name="prefix"></slot>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</template>
|
||||||
|
</a-statistic>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/aCustomStatistic"}}
|
||||||
|
<style>
|
||||||
|
.dark .ant-statistic-content {
|
||||||
|
color: var(--dark-color-text-primary)
|
||||||
|
}
|
||||||
|
.dark .ant-statistic-title {
|
||||||
|
color: rgba(255, 255, 255, 0.55)
|
||||||
|
}
|
||||||
|
.ant-statistic-content {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('a-custom-statistic', {
|
||||||
|
props: {
|
||||||
|
'title': {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
'value': {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `{{template "component/customStatistic"}}`,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||||
:placeholder="placeholder">
|
:placeholder="placeholder">
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;" />
|
<a-icon type="calendar" :style="{ fontSize: '14px', opacity: '0.5' }" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "component/persianDatepicker"}}
|
{{define "component/aPersianDatepicker"}}
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
|
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
|
||||||
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "component/settingListItem"}}
|
{{define "component/settingListItem"}}
|
||||||
<a-list-item :style="{ padding: padding }">
|
<a-list-item :style="{ padding: padding }">
|
||||||
<a-row>
|
<a-row :gutter="[8,16]">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta>
|
<a-list-item-meta>
|
||||||
<template #title>
|
<template #title>
|
||||||
@@ -18,18 +18,10 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "component/setting"}}
|
{{define "component/aSettingListItem"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('a-setting-list-item', {
|
Vue.component('a-setting-list-item', {
|
||||||
props: {
|
props: {
|
||||||
'title': {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
'description': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'paddings': {
|
'paddings': {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
103
web/html/component/aSidebar.html
Normal file
103
web/html/component/aSidebar.html
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{{define "component/sidebar/content"}}
|
||||||
|
<template>
|
||||||
|
<div class="ant-sidebar">
|
||||||
|
<a-layout-sider :theme="themeSwitcher.currentTheme" collapsible :collapsed="collapsed"
|
||||||
|
@collapse="(isCollapsed, type) => collapseHandle(isCollapsed, type)" breakpoint="md">
|
||||||
|
<a-theme-switch></a-theme-switch>
|
||||||
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="activeTab"
|
||||||
|
@click="({key}) => openLink(key)">
|
||||||
|
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||||
|
<a-icon :type="tab.icon"></a-icon>
|
||||||
|
<span v-text="tab.title"></span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-layout-sider>
|
||||||
|
<a-drawer placement="left" :closable="false" @close="closeDrawer" :visible="visible"
|
||||||
|
:wrap-class-name="themeSwitcher.currentTheme" :wrap-style="{ padding: 0 }" :style="{ height: '100%' }">
|
||||||
|
<div class="drawer-handle" @click="toggleDrawer" slot="handle">
|
||||||
|
<a-icon :type="visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
|
</div>
|
||||||
|
<a-theme-switch></a-theme-switch>
|
||||||
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="activeTab"
|
||||||
|
@click="({key}) => openLink(key)">
|
||||||
|
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||||
|
<a-icon :type="tab.icon"></a-icon>
|
||||||
|
<span v-text="tab.title"></span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/aSidebar"}}
|
||||||
|
<style>
|
||||||
|
.ant-sidebar>.ant-layout-sider {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const SIDEBAR_COLLAPSED_KEY = "isSidebarCollapsed"
|
||||||
|
|
||||||
|
Vue.component('a-sidebar', {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}panel/',
|
||||||
|
icon: 'dashboard',
|
||||||
|
title: '{{ i18n "menu.dashboard"}}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}panel/inbounds',
|
||||||
|
icon: 'user',
|
||||||
|
title: '{{ i18n "menu.inbounds"}}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}panel/settings',
|
||||||
|
icon: 'setting',
|
||||||
|
title: '{{ i18n "menu.settings"}}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}panel/xray',
|
||||||
|
icon: 'tool',
|
||||||
|
title: '{{ i18n "menu.xray"}}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}logout/',
|
||||||
|
icon: 'logout',
|
||||||
|
title: '{{ i18n "menu.logout"}}'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeTab: [
|
||||||
|
'{{ .request_uri }}'
|
||||||
|
],
|
||||||
|
visible: false,
|
||||||
|
collapsed: JSON.parse(localStorage.getItem(SIDEBAR_COLLAPSED_KEY)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openLink(key) {
|
||||||
|
return key.startsWith('http') ?
|
||||||
|
window.open(key) :
|
||||||
|
location.href = key
|
||||||
|
},
|
||||||
|
closeDrawer() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
toggleDrawer() {
|
||||||
|
this.visible = !this.visible;
|
||||||
|
},
|
||||||
|
collapseHandle(collapsed, type) {
|
||||||
|
if (type === "clickTrigger") {
|
||||||
|
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, collapsed);
|
||||||
|
|
||||||
|
this.collapsed = JSON.parse(localStorage.getItem(SIDEBAR_COLLAPSED_KEY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `{{template "component/sidebar/content"}}`,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{{define "component/sortableTableTrigger"}}
|
{{define "component/sortableTableTrigger"}}
|
||||||
<a-icon type="drag" class="sortable-icon" style="cursor: move;" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
|
<a-icon type="drag" class="sortable-icon" :style="{ cursor: 'move' }" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
|
||||||
@click="clickHandler" />
|
@click="clickHandler" />
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "component/sortableTable"}}
|
{{define "component/aTableSortable"}}
|
||||||
<script>
|
<script>
|
||||||
const DRAGGABLE_ROW_CLASS = 'draggable-row';
|
const DRAGGABLE_ROW_CLASS = 'draggable-row';
|
||||||
const findParentRowElement = (el) => {
|
const findParentRowElement = (el) => {
|
||||||
@@ -4,15 +4,18 @@
|
|||||||
<a-sub-menu>
|
<a-sub-menu>
|
||||||
<span slot="title">
|
<span slot="title">
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<span>Theme</span>
|
<span>{{ i18n "menu.theme" }}</span>
|
||||||
</span>
|
</span>
|
||||||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark
|
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()">
|
||||||
<a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme"
|
<span>{{ i18n "menu.dark" }}</span>
|
||||||
|
<a-switch :style="{ marginLeft: '2px' }" size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
|
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
|
||||||
@mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;"
|
@mousedown="themeSwitcher.animationsOffUltra()">
|
||||||
:checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
<span>{{ i18n "menu.ultraDark" }}</span>
|
||||||
|
<a-checkbox :style="{ marginLeft: '2px' }" :checked="themeSwitcher.isUltra"
|
||||||
|
@click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-sub-menu>
|
</a-sub-menu>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@@ -21,22 +24,20 @@
|
|||||||
|
|
||||||
{{define "component/themeSwitchTemplateLogin"}}
|
{{define "component/themeSwitchTemplateLogin"}}
|
||||||
<template>
|
<template>
|
||||||
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline"
|
<a-space direction="vertical" :size="10" :style="{ width: '100%' }">
|
||||||
selected-keys="">
|
<a-space direction="horizontal" size="small">
|
||||||
<a-menu-item mode="inline" class="ant-menu-theme-switch">
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
<span>{{ i18n "menu.dark" }}</span>
|
||||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
</a-space>
|
||||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
<a-space v-if="themeSwitcher.isDarkTheme" direction="horizontal" size="small">
|
||||||
<template v-if="themeSwitcher.isDarkTheme">
|
<a-checkbox :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||||
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra"
|
<span>{{ i18n "menu.ultraDark" }}</span>
|
||||||
@click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
|
</a-space>
|
||||||
</template>
|
</a-space>
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "component/themeSwitcher"}}
|
{{define "component/aThemeSwitch"}}
|
||||||
<script>
|
<script>
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number style="width: 50%" v-model.number="client.tgId" min="0"></a-input-number>
|
<a-input-number :style="{ width: '50%' }" v-model.number="client.tgId" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
|
<a-form-item v-if="client.email" label='{{ i18n "comment" }}'>
|
||||||
<a-input v-model.trim="client.comment"></a-input>
|
<a-input v-model.trim="client.comment"></a-input>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
<a-date-picker :style="{ width: '100%' }" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
||||||
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||||
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/outbound"}}
|
{{define "form/outbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
<a-tabs :active-key="outModal.activeKey" :style="{ padding: '0', backgroundColor: 'transparent' }" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
||||||
<a-tab-pane key="1" tab="Form">
|
<a-tab-pane key="1" tab="Form">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
@@ -60,9 +60,9 @@
|
|||||||
<!-- Noise Configurations -->
|
<!-- Noise Configurations -->
|
||||||
<a-form v-for="(noise, index) in outbound.settings.noises" :key="index" :colon="false" :label-col="{ md: {span:8} }"
|
<a-form v-for="(noise, index) in outbound.settings.noises" :key="index" :colon="false" :label-col="{ md: {span:8} }"
|
||||||
:wrapper-col="{ md: {span:14} }">
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;"> Noise [[ index + 1 ]]
|
<a-divider :style="{ margin: '0' }"> Noise [[ index + 1 ]]
|
||||||
<a-icon v-if="outbound.settings.noises.length > 1" type="delete" @click="() => outbound.settings.delNoise(index)"
|
<a-icon v-if="outbound.settings.noises.length > 1" type="delete" @click="() => outbound.settings.delNoise(index)"
|
||||||
style="color: rgb(255, 77, 79); cursor: pointer;"></a-icon>
|
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='Type'>
|
<a-form-item label='Type'>
|
||||||
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
<a-button icon="plus" type="primary" size="small" @click="outbound.settings.addPeer()"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="outbound.settings.addPeer()"></a-button>
|
||||||
</a-form-item>
|
</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-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-icon>
|
<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-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
||||||
<a-input v-model.trim="peer.endpoint"></a-input>
|
<a-input v-model.trim="peer.endpoint"></a-input>
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
{{ i18n "pages.xray.wireguard.allowedIPs" }}
|
{{ i18n "pages.xray.wireguard.allowedIPs" }}
|
||||||
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
|
<template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }">
|
||||||
<a-input v-model.trim="peer.allowedIPs[index]">
|
<a-input v-model.trim="peer.allowedIPs[index]">
|
||||||
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
|
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
@@ -444,10 +444,10 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Short ID">
|
<a-form-item label="Short ID">
|
||||||
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.shortId" :style="{ width: '250px' }"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="SpiderX">
|
<a-form-item label="SpiderX">
|
||||||
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.spiderX" :style="{ width: '250px' }"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Public Key">
|
<a-form-item label="Public Key">
|
||||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||||
@@ -506,12 +506,12 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
<a-form-item style="margin: 10px 0"> Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
<a-space direction="vertical" :size="10" :style="{ marginTop: '10px' }">
|
||||||
<a-button @click="convertLink" type="primary">
|
<a-input addon-before='{{ i18n "pages.xray.outbound.link" }}' v-model.trim="outModal.link" placeholder="vmess:// vless:// trojan:// ss://">
|
||||||
<a-icon type="form"></a-icon>
|
<a-icon slot="addonAfter" type="form" @click="convertLink"></a-icon>
|
||||||
</a-button>
|
</a-input>
|
||||||
</a-form-item>
|
<textarea :style="{ position: 'absolute', left: '-800px' }" id="outboundJson"></textarea>
|
||||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
</a-space>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" :style="{ marginBottom: '10px' }">
|
||||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
|
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<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 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,udp">TCP,UDP</a-select-option>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<a-switch :checked="inbound.settings.auth === 'password'" @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
<a-switch :checked="inbound.settings.auth === 'password'" @change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<table style="width: 100%; text-align: center; margin: 1rem 0;">
|
<table :style="{ width: '100%', textAlign: 'center', margin: '1rem 0' }">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" :style="{ marginBottom: '10px' }">
|
||||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
|
<a-button icon="minus" size="small" @click="inbound.settings.delAccount(index)"></a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
|
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:0;"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
|
<a-divider :style="{ margin: '0' }"> Fallback [[ index + 1 ]] <a-icon type="delete" @click="() => inbound.settings.delFallback(index)" :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
@@ -45,6 +45,6 @@
|
|||||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addPeer()"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-for="(peer, index) in inbound.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="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)" style="color: rgb(255, 77, 79);cursor: pointer;"></a-icon>
|
<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-icon>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
{{ i18n "pages.xray.wireguard.allowedIPs" }}
|
{{ i18n "pages.xray.wireguard.allowedIPs" }}
|
||||||
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="peer.allowedIPs.push('')"></a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
|
<template v-for="(aip, index) in peer.allowedIPs" :style="{ marginBottom: '10px' }">
|
||||||
<a-input v-model.trim="peer.allowedIPs[index]">
|
<a-input v-model.trim="peer.allowedIPs[index]">
|
||||||
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
|
<a-button icon="minus" v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='uTLS'>
|
<a-form-item label='uTLS'>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 100%"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" :style="{ width: '100%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
{{define "form/externalProxy"}}
|
{{define "form/externalProxy"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider style="margin:5px 0 0;"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form-item label="External Proxy">
|
<a-form-item label="External Proxy">
|
||||||
<a-switch v-model="externalProxy"></a-switch>
|
<a-switch v-model="externalProxy"></a-switch>
|
||||||
<a-button icon="plus" 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-button icon="plus" v-if="externalProxy" type="primary" :style="{ marginLeft: '10px' }" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-input-group style="margin: 8px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
|
<a-input-group :style="{ margin: '8px 0' }" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||||
<template>
|
<template>
|
||||||
<a-tooltip title="Force TLS">
|
<a-tooltip title="Force TLS">
|
||||||
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||||
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option value="tls">TLS</a-select-option>
|
<a-select-option value="tls">TLS</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
<a-input :style="{ width: '30%' }" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number :style="{ width: '15%' }" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input style="width: 30%; top: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
<a-input :style="{ width: '30%', top: '0' }" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
|
<a-button icon="minus" size="small" @click="inbound.stream.externalProxy.splice(index, 1)"></a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)"></a-button>
|
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.kcp.type" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None</a-select-option>
|
<a-select-option value="none">None</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP</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="utp">uTP</a-select-option>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" style="width: 75%" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" :style="{ width: '75%' }" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{{define "form/streamSockopt"}}
|
{{define "form/streamSockopt"}}
|
||||||
<a-divider style="margin:5px 0 0;"></a-divider>
|
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Sockopt">
|
<a-form-item label="Sockopt">
|
||||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
@@ -39,17 +39,17 @@
|
|||||||
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Domain Strategy'>
|
<a-form-item label='Domain Strategy'>
|
||||||
<a-select v-model="inbound.stream.sockopt.domainStrategy" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='TCP Congestion'>
|
<a-form-item label='TCP Congestion'>
|
||||||
<a-select v-model="inbound.stream.sockopt.tcpcongestion" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TProxy">
|
<a-form-item label="TProxy">
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="off">Off</a-select-option>
|
<a-select-option value="off">Off</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
<a-select-option value="tproxy">TProxy</a-select-option>
|
<a-select-option value="tproxy">TProxy</a-select-option>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<!-- tcp request -->
|
<!-- tcp request -->
|
||||||
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -33,17 +33,17 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
|
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- tcp response -->
|
<!-- tcp response -->
|
||||||
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -58,10 +58,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
|
<a-button icon="minus" size="small" @click="inbound.stream.tcp.response.removeHeader(index)"></a-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button>
|
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
@@ -11,18 +11,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input :style="{ width: '50%' }" v-model.trim="header.name"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input :style="{ width: '50%' }" v-model.trim="header.value"
|
||||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
|
<a-button icon="minus" slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Mode'>
|
<a-form-item label='Mode'>
|
||||||
<a-select v-model="inbound.stream.xhttp.mode" style="width: 50%"
|
<a-select v-model="inbound.stream.xhttp.mode" :style="{ width: '50%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<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-divider :style="{ margin: '3px 0' }"></a-divider>
|
||||||
<a-form-item label='{{ i18n "security" }}'>
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
@@ -23,18 +23,18 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Min/Max Version">
|
<a-form-item label="Min/Max Version">
|
||||||
<a-input-group compact>
|
<a-input-group compact>
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 50%"
|
<a-select v-model="inbound.stream.tls.minVersion" :style="{ width: '50%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 50%"
|
<a-select v-model="inbound.stream.tls.maxVersion" :style="{ width: '50%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<a-form-item label="uTLS">
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 100%"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" :style="{ width: '100%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
@@ -67,9 +67,9 @@
|
|||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
|
<a-button icon="plus" v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
|
||||||
style="margin-left: 10px"></a-button>
|
:style="{ marginLeft: '10px' }"></a-button>
|
||||||
<a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
|
<a-button icon="minus" v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
|
||||||
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px"></a-button>
|
@click="inbound.stream.tls.removeCert(index)" :style="{ marginLeft: '10px' }"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<template v-if="cert.useFile">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Usage Option'>
|
<a-form-item label='Usage Option'>
|
||||||
<a-select v-model="cert.usage" style="width: 50%" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="cert.usage" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in USAGE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -43,6 +43,15 @@
|
|||||||
margin:-10px 2px !important;
|
margin:-10px 2px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dark .ant-switch-small:not(.ant-switch-checked) {
|
||||||
|
background-color: var(--dark-color-surface-100) !important;
|
||||||
|
}
|
||||||
|
.ant-custom-popover-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin: 0.5rem -2rem 0.5rem 2rem;
|
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||||
}
|
}
|
||||||
@@ -124,12 +133,12 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
<a-sidebar></a-sidebar>
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
color="red"
|
color="red"
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
@@ -137,406 +146,433 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card size="small" :style="{ padding: '16px' }" hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :sm="12" :md="6">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
<a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
|
||||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up) ]] / [[ SizeFormatter.sizeFormat(total.down) ]]</a-tag>
|
<template #prefix>
|
||||||
|
<a-icon type="swap"></a-icon>
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :sm="12" :md="6">
|
||||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
<a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up + total.down) ]]</a-tag>
|
<template #prefix>
|
||||||
|
<a-icon type="pie-chart"></a-icon>
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :sm="12" :md="6">
|
||||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
<a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
<template #prefix>
|
||||||
|
<a-icon type="bars"></a-icon>
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :sm="12" :md="6">
|
||||||
<template>
|
<a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||||
<div>
|
<template #prefix>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
|
<a-space direction="horizontal">
|
||||||
{{ i18n "clients" }}:
|
<a-icon type="team"></a-icon>
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<div>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
|
||||||
<template slot="content">
|
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
|
||||||
</a-popover>
|
</template>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
<template slot="content">
|
</a-popover>
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
|
||||||
</a-popover>
|
</template>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
<template slot="content">
|
</a-popover>
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
<div v-for="clientEmail in total.expiring"><span>[[ clientEmail ]]</span></div>
|
||||||
</a-popover>
|
</template>
|
||||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
<template slot="content">
|
</a-popover>
|
||||||
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
<div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
|
||||||
</a-popover>
|
</template>
|
||||||
</div>
|
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||||
</template>
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<template #title>
|
||||||
<a-row>
|
<a-space direction="horizontal">
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
</a-button>
|
||||||
</a-button>
|
|
||||||
<a-dropdown :trigger="['click']">
|
|
||||||
<a-button type="primary" icon="menu">
|
|
||||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
|
||||||
</a-button>
|
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
|
||||||
<a-menu-item key="import">
|
|
||||||
<a-icon type="import"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.importInbound" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="export">
|
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="resetInbounds">
|
|
||||||
<a-icon type="reload"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="resetClients">
|
|
||||||
<a-icon type="file-done"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
|
||||||
<a-icon type="rest"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</a-col>
|
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
|
||||||
<a-select v-model="refreshInterval"
|
|
||||||
style="width: 65px;"
|
|
||||||
v-if="isRefreshEnabled"
|
|
||||||
@change="changeRefreshInterval"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
|
||||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
|
||||||
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
|
|
||||||
<a-switch v-model="enableFilter"
|
|
||||||
: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>
|
|
||||||
</a-switch>
|
|
||||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
|
||||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
|
||||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
|
||||||
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
<a-back-top></a-back-top>
|
|
||||||
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
|
||||||
:data-source="searchedInbounds"
|
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
|
||||||
:pagination=pagination(searchedInbounds)
|
|
||||||
:expand-icon-as-cell="false"
|
|
||||||
:expand-row-by-click="false"
|
|
||||||
:expand-icon-column-index="0"
|
|
||||||
:indent-size="0"
|
|
||||||
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
|
||||||
style="margin-top: 10px">
|
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
|
<a-button type="primary" icon="menu">
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||||
<a-menu-item key="edit">
|
</a-button>
|
||||||
<a-icon type="edit"></a-icon>
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
{{ i18n "edit" }}
|
<a-menu-item key="import">
|
||||||
|
<a-icon type="import"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.importInbound" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
<a-menu-item key="export">
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isMultiUser()">
|
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||||
<a-menu-item key="addClient">
|
<a-icon type="export"></a-icon>
|
||||||
<a-icon type="user-add"></a-icon>
|
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
||||||
{{ i18n "pages.client.add"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="addBulkClient">
|
|
||||||
<a-icon type="usergroup-add"></a-icon>
|
|
||||||
{{ i18n "pages.client.bulk"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="resetClients">
|
|
||||||
<a-icon type="file-done"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="export">
|
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
|
||||||
<a-icon type="export"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
|
||||||
<a-icon type="rest"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
|
||||||
</a-menu-item>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-menu-item key="showInfo">
|
|
||||||
<a-icon type="info-circle"></a-icon>
|
|
||||||
{{ i18n "info"}}
|
|
||||||
</a-menu-item>
|
|
||||||
</template>
|
|
||||||
<a-menu-item key="clipboard">
|
|
||||||
<a-icon type="copy"></a-icon>
|
|
||||||
{{ i18n "pages.inbounds.exportInbound" }}
|
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="resetTraffic">
|
<a-menu-item key="resetInbounds">
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="reload"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="clone">
|
<a-menu-item key="resetClients">
|
||||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
<a-icon type="file-done"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delete">
|
<a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
|
||||||
<span style="color: #FF4D4F">
|
<a-icon type="rest"></a-icon>
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item v-if="isMobile">
|
|
||||||
<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-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</a-space>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
</template>
|
||||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
<template #extra>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<a-button-group>
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-button icon="sync" @click="manualRefresh" :loading="refreshing"></a-button>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
<a-popover placement="bottomRight" trigger="click" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
<template #title>
|
||||||
|
<div class="ant-custom-popover-title">
|
||||||
|
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh" size="small"></a-switch>
|
||||||
|
<span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
|
||||||
|
<a-select v-model="refreshInterval"
|
||||||
|
:disabled="!isRefreshEnabled"
|
||||||
|
:style="{ width: '100%' }"
|
||||||
|
@change="changeRefreshInterval"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-button icon="down"></a-button>
|
||||||
|
</a-popover>
|
||||||
|
</a-button-group>
|
||||||
|
</template>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<div :style="isMobile ? {} : { display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }">
|
||||||
|
<a-switch v-model="enableFilter"
|
||||||
|
:style="isMobile ? { marginBottom: '.5rem', display: 'flex' } : { marginRight: '.5rem' }"
|
||||||
|
@change="toggleFilter">
|
||||||
|
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||||
|
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||||
|
</a-switch>
|
||||||
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus :style="{ maxWidth: '300px' }" :size="isMobile ? 'small' : ''"></a-input>
|
||||||
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
||||||
|
:data-source="searchedInbounds"
|
||||||
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
|
:pagination=pagination(searchedInbounds)
|
||||||
|
:expand-icon-as-cell="false"
|
||||||
|
:expand-row-by-click="false"
|
||||||
|
:expand-icon-column-index="0"
|
||||||
|
:indent-size="0"
|
||||||
|
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||||
|
:style="{ marginTop: '10px' }">
|
||||||
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more" :style="{ fontSize: '20px', textDecoration: 'solid' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item key="edit">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
||||||
|
<a-icon type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<template v-if="dbInbound.isMultiUser()">
|
||||||
|
<a-menu-item key="addClient">
|
||||||
|
<a-icon type="user-add"></a-icon>
|
||||||
|
{{ i18n "pages.client.add"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="addBulkClient">
|
||||||
|
<a-icon type="usergroup-add"></a-icon>
|
||||||
|
{{ i18n "pages.client.bulk"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetClients">
|
||||||
|
<a-icon type="file-done"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="export">
|
||||||
|
<a-icon type="export"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.export"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||||
|
<a-icon type="export"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delDepletedClients" :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="rest"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-menu-item key="showInfo">
|
||||||
|
<a-icon type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
<a-menu-item key="clipboard">
|
||||||
|
<a-icon type="copy"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.exportInbound" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetTraffic">
|
||||||
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="clone">
|
||||||
|
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delete">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isMobile">
|
||||||
|
<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>
|
||||||
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<a-tag :style="{ margin: '0' }" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag :style="{ margin: '0' }" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||||
|
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
|
<a-tag :style="{ margin: '0' }" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<div v-for="clientEmail in clientCount[dbInbound.id].deactive"><span>[[ clientEmail ]]</span></div>
|
||||||
|
</template>
|
||||||
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<div v-for="clientEmail in clientCount[dbInbound.id].depleted"><span>[[ clientEmail ]]</span></div>
|
||||||
|
</template>
|
||||||
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<div v-for="clientEmail in clientCount[dbInbound.id].expiring"><span>[[ clientEmail ]]</span></div>
|
||||||
|
</template>
|
||||||
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<div v-for="clientEmail in clientCount[dbInbound.id].online"><span>[[ clientEmail ]]</span></div>
|
||||||
|
</template>
|
||||||
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||||
|
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
</a-popover>
|
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<template v-if="dbInbound.total > 0">
|
||||||
<template slot="content">
|
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
</template>
|
||||||
</template>
|
<template v-else>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
</a-popover>
|
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
</svg>
|
||||||
<template slot="content">
|
</template>
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
</a-tag>
|
||||||
</template>
|
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
|
||||||
</a-popover>
|
|
||||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
|
||||||
</template>
|
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
</template>
|
||||||
<template slot="content">
|
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||||
<table cellpadding="2" width="100%">
|
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<tr>
|
<template slot="content" v-if="app.datepicker === 'gregorian'">
|
||||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
|
||||||
<td>{{ i18n "remained" }}</td>
|
|
||||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</template>
|
|
||||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
|
||||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
|
||||||
<template v-if="dbInbound.total > 0">
|
|
||||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else slot="content">
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
<a-tag :style="{ minWidth: '50px' }" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||||
|
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else color="purple" class="infinite-tag">
|
||||||
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
|
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-popover>
|
</template>
|
||||||
</template>
|
<template slot="info" slot-scope="text, dbInbound">
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
<template slot="content">
|
||||||
</template>
|
<table cellpadding="2">
|
||||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
<tr>
|
||||||
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||||
<template slot="content" v-if="app.datepicker === 'gregorian'">
|
<td>
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
<a-tag :style="{ margin: '0' }" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
</template>
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<template v-else slot="content">
|
<a-tag :style="{ margin: '0' }" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||||
</template>
|
<a-tag :style="{ margin: '0' }" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||||
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
|
||||||
[[ remainedDays(dbInbound._expiryTime) ]]
|
|
||||||
</a-tag>
|
|
||||||
</a-popover>
|
|
||||||
<a-tag v-else color="purple" class="infinite-tag">
|
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
|
||||||
<template slot="info" slot-scope="text, dbInbound">
|
|
||||||
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
|
||||||
<template slot="content">
|
|
||||||
<table cellpadding="2">
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
|
||||||
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="clientCount[dbInbound.id]">
|
|
||||||
<td>{{ i18n "clients" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
</td>
|
||||||
</a-popover>
|
</tr>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<tr>
|
||||||
<template slot="content">
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||||
</template>
|
</tr>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<tr v-if="clientCount[dbInbound.id]">
|
||||||
</a-popover>
|
<td>{{ i18n "clients" }}</td>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<td>
|
||||||
<template slot="content">
|
<a-tag :style="{ margin: '0' }" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</a-popover>
|
</template>
|
||||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
<template slot="content">
|
</a-popover>
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</template>
|
<template slot="content">
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</a-popover>
|
</template>
|
||||||
</td>
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</tr>
|
</a-popover>
|
||||||
<tr>
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
<template slot="content">
|
||||||
<td>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
</template>
|
||||||
<template slot="content">
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
<table cellpadding="2" width="100%">
|
</a-popover>
|
||||||
<tr>
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
<template slot="content">
|
||||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
</tr>
|
</template>
|
||||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
<a-tag :style="{ margin: '0', padding: '0 2px' }" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
<td>{{ i18n "remained" }}</td>
|
</a-popover>
|
||||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
<tr>
|
||||||
</template>
|
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
<td>
|
||||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template v-if="dbInbound.total > 0">
|
<template slot="content">
|
||||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||||
|
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
|
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
|
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag :style="{ minWidth: '50px', textAlign: 'center' }" v-if="dbInbound.expiryTime > 0"
|
||||||
|
:color="dbInbound.isExpiry? 'red': 'blue'">
|
||||||
|
<template v-if="app.datepicker === 'gregorian'">
|
||||||
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-popover>
|
<a-tag v-else :style="{ textAlign: 'center' }" color="purple" class="infinite-tag">
|
||||||
</td>
|
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||||
</tr>
|
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||||
<tr>
|
</svg>
|
||||||
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
</a-tag>
|
||||||
<td>
|
</td>
|
||||||
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
|
</tr>
|
||||||
:color="dbInbound.isExpiry? 'red': 'blue'">
|
</table>
|
||||||
<template v-if="app.datepicker === 'gregorian'">
|
</template>
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
<a-badge>
|
||||||
</template>
|
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="{ color: themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc' }"></a-icon>
|
||||||
<template v-else>
|
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
||||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
<a-icon type="info"></a-icon>
|
||||||
</template>
|
</a-button>
|
||||||
</a-tag>
|
</a-badge>
|
||||||
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
|
</a-popover>
|
||||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
</template>
|
||||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
</svg>
|
<a-table
|
||||||
</a-tag>
|
:row-key="client => client.id"
|
||||||
</td>
|
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||||
</tr>
|
:data-source="getInboundClients(record)"
|
||||||
</table>
|
:pagination=pagination(getInboundClients(record))
|
||||||
</template>
|
:style="{ margin: `-10px ${isMobile ? '2px' : '22px'} -11px` }">
|
||||||
<a-badge>
|
{{template "component/aClientTable"}}
|
||||||
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
</a-table>
|
||||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
</template>
|
||||||
<a-icon type="info"></a-icon>
|
</a-table>
|
||||||
</a-button>
|
</a-space>
|
||||||
</a-badge>
|
|
||||||
</a-popover>
|
|
||||||
</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: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
|
|
||||||
{{template "client_table"}}
|
|
||||||
</a-table>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -548,8 +584,10 @@
|
|||||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/aSidebar" .}}
|
||||||
{{template "component/persianDatepicker" .}}
|
{{template "component/aThemeSwitch" .}}
|
||||||
|
{{template "component/aCustomStatistic" .}}
|
||||||
|
{{template "component/aPersianDatepicker" .}}
|
||||||
<script>
|
<script>
|
||||||
const columns = [{
|
const columns = [{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
@@ -640,8 +678,8 @@
|
|||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
mixins: [MediaQueryMixin],
|
||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
|
||||||
themeSwitcher,
|
themeSwitcher,
|
||||||
persianDatepicker,
|
persianDatepicker,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
@@ -662,6 +700,7 @@
|
|||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
|
subTitle : '',
|
||||||
subURI : '',
|
subURI : '',
|
||||||
subJsonURI : '',
|
subJsonURI : '',
|
||||||
},
|
},
|
||||||
@@ -671,7 +710,6 @@
|
|||||||
showAlert: false,
|
showAlert: false,
|
||||||
ipLimitEnable: false,
|
ipLimitEnable: false,
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
isMobile: window.innerWidth <= 768,
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -711,6 +749,7 @@
|
|||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
|
subTitle : subTitle,
|
||||||
subURI: subURI,
|
subURI: subURI,
|
||||||
subJsonURI: subJsonURI
|
subJsonURI: subJsonURI
|
||||||
};
|
};
|
||||||
@@ -1432,9 +1471,6 @@
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
|
||||||
onResize() {
|
|
||||||
this.isMobile = window.innerWidth <= 768;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -1446,8 +1482,6 @@
|
|||||||
if (window.location.protocol !== "https:") {
|
if (window.location.protocol !== "https:") {
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', this.onResize);
|
|
||||||
this.onResize();
|
|
||||||
this.loading();
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
if (this.isRefreshEnabled) {
|
if (this.isRefreshEnabled) {
|
||||||
@@ -1485,12 +1519,12 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "modals/inboundModal"}}
|
||||||
{{template "promptModal"}}
|
{{template "modals/promptModal"}}
|
||||||
{{template "qrcodeModal"}}
|
{{template "modals/qrcodeModal"}}
|
||||||
{{template "textModal"}}
|
{{template "modals/textModal"}}
|
||||||
{{template "inboundInfoModal"}}
|
{{template "modals/inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "modals/clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "modals/clientsBulkModal"}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
750
web/html/index.html
Normal file
750
web/html/index.html
Normal file
@@ -0,0 +1,750 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "head" .}}
|
||||||
|
<style>
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.ant-layout-content {
|
||||||
|
margin: 24px 16px;
|
||||||
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
|
.ant-alert-error {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-col-sm-24 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.ant-card-dark h2 {
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
|
}
|
||||||
|
.ant-backup-list-item {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.ant-xray-version-list-item {
|
||||||
|
--padding: 12px;
|
||||||
|
padding: var(--padding) !important;
|
||||||
|
gap: var(--padding);
|
||||||
|
}
|
||||||
|
.dark .ant-backup-list-item svg,
|
||||||
|
.dark .ant-badge-status-text,
|
||||||
|
.dark .ant-card-extra {
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
|
}
|
||||||
|
.dark .ant-card-actions>li {
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
}
|
||||||
|
.dark .ant-radio-inner {
|
||||||
|
background-color: var(--dark-color-surface-100);
|
||||||
|
border-color: var(--dark-color-surface-600);
|
||||||
|
}
|
||||||
|
.dark .ant-radio-checked .ant-radio-inner {
|
||||||
|
border-color: var(--color-primary-100);
|
||||||
|
}
|
||||||
|
.dark .ant-backup-list,
|
||||||
|
.dark .ant-xray-version-list,
|
||||||
|
.dark .ant-card-actions,
|
||||||
|
.dark .ant-card-actions>li:not(:last-child) {
|
||||||
|
border-color: var(--dark-color-stroke);
|
||||||
|
}
|
||||||
|
.ant-card-actions {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.ip-hidden {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
filter: blur(10px);
|
||||||
|
}
|
||||||
|
.running-animation .ant-badge-status-dot {
|
||||||
|
animation: runningAnimation 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
.running-animation .ant-badge-status-processing:after {
|
||||||
|
border-color: var(--color-primary-100);
|
||||||
|
}
|
||||||
|
@keyframes runningAnimation {
|
||||||
|
0%,
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
transform: scale(1.5);
|
||||||
|
opacity: .2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
|
<a-sidebar></a-sidebar>
|
||||||
|
<a-layout id="content-layout">
|
||||||
|
<a-layout-content>
|
||||||
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip">
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<template>
|
||||||
|
<a-row v-if="!status.isLoaded">
|
||||||
|
<a-card hoverable :style="{ textAlign: 'center', padding: '30px 0', marginTop: '10px', background: 'transparent' }">
|
||||||
|
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
||||||
|
</a-card>
|
||||||
|
</a-row>
|
||||||
|
<a-row v-else>
|
||||||
|
<a-row>
|
||||||
|
<a-card hoverable>
|
||||||
|
<a-row>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12" :style="{ textAlign: 'center' }">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.cpu.color"
|
||||||
|
:percent="status.cpu.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.cpu" }}:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<a-icon type="area-chart"></a-icon>
|
||||||
|
<template slot="title">
|
||||||
|
<div><b>{{ i18n "pages.index.logicalProcessors" }}:</b> [[ (status.logicalPro) ]]</div>
|
||||||
|
<div><b>{{ i18n "pages.index.frequency" }}:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" :style="{ textAlign: 'center' }">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.mem.color"
|
||||||
|
:percent="status.mem.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12" :style="{ textAlign: 'center' }">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.swap.color"
|
||||||
|
:percent="status.swap.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.swap" }}:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" :style="{ textAlign: 'center' }">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.disk.color"
|
||||||
|
:percent="status.disk.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.storage"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-row>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card hoverable>
|
||||||
|
<template #title>
|
||||||
|
<a-space direction="horizontal">
|
||||||
|
<span>{{ i18n "pages.index.xrayStatus" }}</span>
|
||||||
|
<a-tag v-if="isMobile && status.xray.version != 'Unknown'" color="green">
|
||||||
|
v[[ status.xray.version ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<template v-if="status.xray.state != 'error'">
|
||||||
|
<a-badge status="processing" class="running-animation" :text="status.xray.stateMsg" :color="status.xray.color"/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<span slot="title">
|
||||||
|
<a-row type="flex" align="middle" justify="space-between">
|
||||||
|
<a-col>
|
||||||
|
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||||
|
</a-col>
|
||||||
|
<a-col>
|
||||||
|
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</span>
|
||||||
|
<template slot="content">
|
||||||
|
<span :style="{ maxWidth: '400px' }" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-badge :text="status.xray.stateMsg" :color="status.xray.color"/>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="poweroff"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
|
||||||
|
</a-space>
|
||||||
|
<a-space direction="horizontal" @click="restartXrayService" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="reload"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.restartXray" }}</span>
|
||||||
|
</a-space>
|
||||||
|
<a-space direction="horizontal" @click="openSelectV2rayVersion" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="tool"></a-icon>
|
||||||
|
<span v-if="!isMobile">
|
||||||
|
[[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]]
|
||||||
|
</span>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "menu.link" }}' hoverable>
|
||||||
|
<template #actions>
|
||||||
|
<a-space direction="horizontal" @click="openLogs()" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="bars"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
|
||||||
|
</a-space>
|
||||||
|
<a-space direction="horizontal" @click="openConfig" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="control"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.config" }}</span>
|
||||||
|
</a-space>
|
||||||
|
<a-space direction="horizontal" @click="openBackup" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="cloud-server"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.backup" }}</span>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='3X-UI' hoverable>
|
||||||
|
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
|
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.operationHours" }}' hoverable>
|
||||||
|
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
|
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.systemLoad" }}' hoverable>
|
||||||
|
<a-tag color="green">
|
||||||
|
<a-tooltip>
|
||||||
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "usage"}}' hoverable>
|
||||||
|
<a-tag color="green"> {{ i18n "pages.index.memory" }}: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||||
|
<a-tag color="green"> {{ i18n "pages.index.threads" }}: [[ status.appStats.threads ]] </a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.overallSpeed" }}' hoverable>
|
||||||
|
<a-row :gutter="isMobile ? [8,8] : 0">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title='{{ i18n "pages.index.upload" }}' :value="SizeFormatter.sizeFormat(status.netIO.up)">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="arrow-up" />
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
/s
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title='{{ i18n "pages.index.download" }}' :value="SizeFormatter.sizeFormat(status.netIO.down)">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="arrow-down" />
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
/s
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.totalData" }}' hoverable>
|
||||||
|
<a-row :gutter="isMobile ? [8,8] : 0">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title='{{ i18n "pages.index.sent" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.sent)">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="cloud-upload" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title='{{ i18n "pages.index.received" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.recv)">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="cloud-download" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.ipAddresses" }}' hoverable>
|
||||||
|
<template #extra>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>
|
||||||
|
{{ i18n "pages.index.toggleIpVisibility" }}
|
||||||
|
</template>
|
||||||
|
<a-icon :type="showIp ? 'eye' : 'eye-invisible'" :style="{ fontSize: '1rem' }" @click="showIp = !showIp"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-row :class="showIp ? 'ip-visible' : 'ip-hidden'" :gutter="isMobile ? [8,8] : 0">
|
||||||
|
<a-col :span="isMobile ? 24 : 12">
|
||||||
|
<a-custom-statistic title="IPv4" :value="status.publicIP.ipv4">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="global" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="isMobile ? 24 : 12">
|
||||||
|
<a-custom-statistic title="IPv6" :value="status.publicIP.ipv6">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="global" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card title='{{ i18n "pages.index.connectionCount" }}' hoverable>
|
||||||
|
<a-row :gutter="isMobile ? [8,8] : 0">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title="TCP" :value="status.tcpCount">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="swap" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-custom-statistic title="UDP" :value="status.udpCount">
|
||||||
|
<template #prefix>
|
||||||
|
<a-icon type="swap" />
|
||||||
|
</template>
|
||||||
|
</a-custom-statistic>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</transition>
|
||||||
|
</a-spin>
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
||||||
|
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
||||||
|
<a-alert type="warning" :style="{ marginBottom: '12px', width: '100%' }"
|
||||||
|
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
||||||
|
<a-list class="ant-xray-version-list" bordered :style="{ width: '100%' }">
|
||||||
|
<a-list-item class="ant-xray-version-list-item" v-for="version, index in versionModal.versions">
|
||||||
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag>
|
||||||
|
<a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
|
||||||
|
</a-list-item>
|
||||||
|
</a-list>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal id="log-modal" v-model="logModal.visible"
|
||||||
|
:closable="true" @cancel="() => logModal.visible = false"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
|
width="800px" footer="">
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.logs" }}
|
||||||
|
<a-icon :spin="logModal.loading"
|
||||||
|
type="sync"
|
||||||
|
:style="{ verticalAlign: 'middle', marginLeft: '10px' }"
|
||||||
|
:disabled="logModal.loading"
|
||||||
|
@click="openLogs()">
|
||||||
|
</a-icon>
|
||||||
|
</template>
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-form-item :style="{ marginRight: '0.5rem' }">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-select size="small" v-model="logModal.rows" :style="{ width: '70px' }"
|
||||||
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="10">10</a-select-option>
|
||||||
|
<a-select-option value="20">20</a-select-option>
|
||||||
|
<a-select-option value="50">50</a-select-option>
|
||||||
|
<a-select-option value="100">100</a-select-option>
|
||||||
|
<a-select-option value="500">500</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select size="small" v-model="logModal.level" :style="{ width: '95px' }"
|
||||||
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
|
<a-select-option value="err">Error</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :style="{ float: 'right' }">
|
||||||
|
<a-button type="primary" icon="download" @click="FileManager.downloadTextFile(logModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal id="backup-modal"
|
||||||
|
v-model="backupModal.visible"
|
||||||
|
title='{{ i18n "pages.index.backupTitle" }}'
|
||||||
|
:closable="true"
|
||||||
|
footer=""
|
||||||
|
:class="themeSwitcher.currentTheme">
|
||||||
|
<a-list class="ant-backup-list" bordered :style="{ width: '100%' }">
|
||||||
|
<a-list-item class="ant-backup-list-item">
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #title>{{ i18n "pages.index.exportDatabase" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
<a-button @click="exportDatabase()" type="primary" icon="download"/>
|
||||||
|
</a-list-item>
|
||||||
|
<a-list-item class="ant-backup-list-item">
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #title>{{ i18n "pages.index.importDatabase" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
<a-button @click="importDatabase()" type="primary" icon="upload" />
|
||||||
|
</a-list-item>
|
||||||
|
</a-list>
|
||||||
|
</a-modal>
|
||||||
|
</a-layout>
|
||||||
|
{{template "js" .}}
|
||||||
|
{{template "component/aSidebar" .}}
|
||||||
|
{{template "component/aThemeSwitch" .}}
|
||||||
|
{{template "component/aCustomStatistic" .}}
|
||||||
|
{{template "modals/textModal"}}
|
||||||
|
<script>
|
||||||
|
class CurTotal {
|
||||||
|
|
||||||
|
constructor(current, total) {
|
||||||
|
this.current = current;
|
||||||
|
this.total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
get percent() {
|
||||||
|
if (this.total === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return NumberFormatter.toFixed(this.current / this.total * 100, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get color() {
|
||||||
|
const percent = this.percent;
|
||||||
|
if (percent < 80) {
|
||||||
|
return '#008771'; // Green
|
||||||
|
} else if (percent < 90) {
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
} else {
|
||||||
|
return "#cf3c3c"; // Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Status {
|
||||||
|
constructor(data, isLoaded = false) {
|
||||||
|
this.cpu = new CurTotal(0, 0);
|
||||||
|
this.cpuCores = 0;
|
||||||
|
this.logicalPro = 0;
|
||||||
|
this.cpuSpeedMhz = 0;
|
||||||
|
this.disk = new CurTotal(0, 0);
|
||||||
|
this.loads = [0, 0, 0];
|
||||||
|
this.mem = new CurTotal(0, 0);
|
||||||
|
this.netIO = { up: 0, down: 0 };
|
||||||
|
this.netTraffic = { sent: 0, recv: 0 };
|
||||||
|
this.publicIP = { ipv4: 0, ipv6: 0 };
|
||||||
|
this.swap = new CurTotal(0, 0);
|
||||||
|
this.tcpCount = 0;
|
||||||
|
this.udpCount = 0;
|
||||||
|
this.uptime = 0;
|
||||||
|
this.appUptime = 0;
|
||||||
|
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
||||||
|
|
||||||
|
this.xray = { state: 'stop', stateMsg: "", errorMsg: "", version: "", color: "" };
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoaded = isLoaded;
|
||||||
|
this.cpu = new CurTotal(data.cpu, 100);
|
||||||
|
this.cpuCores = data.cpuCores;
|
||||||
|
this.logicalPro = data.logicalPro;
|
||||||
|
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
||||||
|
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
||||||
|
this.loads = data.loads.map(load => NumberFormatter.toFixed(load, 2));
|
||||||
|
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
||||||
|
this.netIO = data.netIO;
|
||||||
|
this.netTraffic = data.netTraffic;
|
||||||
|
this.publicIP = data.publicIP;
|
||||||
|
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
||||||
|
this.tcpCount = data.tcpCount;
|
||||||
|
this.udpCount = data.udpCount;
|
||||||
|
this.uptime = data.uptime;
|
||||||
|
this.appUptime = data.appUptime;
|
||||||
|
this.appStats = data.appStats;
|
||||||
|
this.xray = data.xray;
|
||||||
|
switch (this.xray.state) {
|
||||||
|
case 'running':
|
||||||
|
this.xray.color = "green";
|
||||||
|
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusRunning" }}';
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
this.xray.color = "orange";
|
||||||
|
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusStop" }}';
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
this.xray.color = "red";
|
||||||
|
this.xray.stateMsg ='{{ i18n "pages.index.xrayStatusError" }}';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.xray.color = "gray";
|
||||||
|
this.xray.stateMsg = '{{ i18n "pages.index.xrayStatusUnknown" }}';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionModal = {
|
||||||
|
visible: false,
|
||||||
|
versions: [],
|
||||||
|
show(versions) {
|
||||||
|
this.visible = true;
|
||||||
|
this.versions = versions;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const logModal = {
|
||||||
|
visible: false,
|
||||||
|
logs: [],
|
||||||
|
rows: 20,
|
||||||
|
level: 'info',
|
||||||
|
syslog: false,
|
||||||
|
loading: false,
|
||||||
|
show(logs) {
|
||||||
|
this.visible = true;
|
||||||
|
this.logs = logs;
|
||||||
|
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||||
|
},
|
||||||
|
formatLogs(logs) {
|
||||||
|
let formattedLogs = '';
|
||||||
|
const levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
|
||||||
|
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
|
||||||
|
|
||||||
|
logs.forEach((log, index) => {
|
||||||
|
let [data, message] = log.split(" - ",2);
|
||||||
|
const parts = data.split(" ")
|
||||||
|
if(index>0) formattedLogs += '<br>';
|
||||||
|
|
||||||
|
if (parts.length === 3) {
|
||||||
|
const d = parts[0];
|
||||||
|
const t = parts[1];
|
||||||
|
const level = parts[2];
|
||||||
|
const levelIndex = levels.indexOf(level,levels) || 5;
|
||||||
|
|
||||||
|
//formattedLogs += `<span style="color: gray;">${index + 1}.</span>`;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[0]};">${d} ${t}</span> `;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${level}</span>`;
|
||||||
|
} else {
|
||||||
|
const levelIndex = levels.indexOf(data,levels) || 5;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${data}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message){
|
||||||
|
if(message.startsWith("XRAY:"))
|
||||||
|
message = "<b>XRAY: </b>" + message.substring(5);
|
||||||
|
else
|
||||||
|
message = "<b>X-UI: </b>" + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedLogs += message ? ' - ' + message : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedLogs;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const backupModal = {
|
||||||
|
visible: false,
|
||||||
|
show() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
mixins: [MediaQueryMixin],
|
||||||
|
data: {
|
||||||
|
themeSwitcher,
|
||||||
|
status: new Status(),
|
||||||
|
versionModal,
|
||||||
|
logModal,
|
||||||
|
backupModal,
|
||||||
|
spinning: false,
|
||||||
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
|
showAlert: false,
|
||||||
|
showIp: false
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||||
|
this.spinning = spinning;
|
||||||
|
this.loadingTip = tip;
|
||||||
|
},
|
||||||
|
async getStatus() {
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.post('/server/status');
|
||||||
|
if (msg.success) {
|
||||||
|
this.setStatus(msg.obj, true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to get status:", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStatus(data, isLoaded = false) {
|
||||||
|
this.status = new Status(data, isLoaded);
|
||||||
|
},
|
||||||
|
async openSelectV2rayVersion() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/getXrayVersion');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
versionModal.show(msg.obj);
|
||||||
|
},
|
||||||
|
switchV2rayVersion(version) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
|
okText: '{{ i18n "confirm"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: async () => {
|
||||||
|
versionModal.hide();
|
||||||
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
|
await HttpUtil.post(`/server/installXray/${version}`);
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async stopXrayService() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/stopXrayService');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartXrayService() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/restartXrayService');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openLogs(){
|
||||||
|
logModal.loading = true;
|
||||||
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logModal.show(msg.obj);
|
||||||
|
await PromiseUtil.sleep(500);
|
||||||
|
logModal.loading = false;
|
||||||
|
},
|
||||||
|
async openConfig() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('server/getConfigJson');
|
||||||
|
this.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
||||||
|
},
|
||||||
|
openBackup() {
|
||||||
|
backupModal.show();
|
||||||
|
},
|
||||||
|
exportDatabase() {
|
||||||
|
window.location = basePath + 'server/getDb';
|
||||||
|
},
|
||||||
|
importDatabase() {
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = '.db';
|
||||||
|
fileInput.addEventListener('change', async (event) => {
|
||||||
|
const dbFile = event.target.files[0];
|
||||||
|
if (dbFile) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('db', dbFile);
|
||||||
|
backupModal.hide();
|
||||||
|
this.loading(true);
|
||||||
|
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.loading(false);
|
||||||
|
if (!uploadMsg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading(true);
|
||||||
|
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
if (restartMsg.success) {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(5000);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileInput.click();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await this.getStatus();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await PromiseUtil.sleep(2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -6,27 +6,33 @@
|
|||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
/* margin: 20px 0 50px 0;*/
|
/* margin: 20px 0 50px 0;*/
|
||||||
height: 110px;
|
height: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn,
|
.ant-btn,
|
||||||
.ant-input {
|
.ant-input {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon {
|
.ant-input-group-addon {
|
||||||
border-radius: 0 30px 30px 0;
|
border-radius: 0 30px 30px 0;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-affix-wrapper .ant-input-prefix {
|
.ant-input-affix-wrapper .ant-input-prefix {
|
||||||
left: 23px;
|
left: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centered {
|
.centered {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -34,68 +40,76 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title b {
|
.title b {
|
||||||
font-weight: bold !important;
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login {
|
#login {
|
||||||
animation: charge 0.5s both;
|
animation: charge 0.5s both;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
padding: 3rem;
|
padding: 3rem;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
user-select:none;
|
user-select: none;
|
||||||
-webkit-user-select:none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login:hover {
|
#login:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes charge {
|
@keyframes charge {
|
||||||
from {
|
from {
|
||||||
transform: translateY(5rem);
|
transform: translateY(5rem);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.under {
|
.under {
|
||||||
background-color: #c7ebe2;
|
background-color: #c7ebe2;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .under {
|
.dark .under {
|
||||||
background-color: var(--dark-color-login-wave);
|
background-color: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark #login {
|
.dark #login {
|
||||||
background-color: var(--dark-color-surface-100);
|
background-color: var(--dark-color-surface-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark h1 {
|
.dark h1 {
|
||||||
color: rgba(255, 255, 255);
|
color: rgba(255, 255, 255);
|
||||||
}
|
}
|
||||||
.ant-form-item {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.ant-btn-primary-login {
|
.ant-btn-primary-login {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-primary-login:focus,
|
.ant-btn-primary-login:focus,
|
||||||
.ant-btn-primary-login:hover {
|
.ant-btn-primary-login:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #006655;
|
background-color: #006655;
|
||||||
border-color: #006655;
|
border-color: #006655;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(270deg,
|
||||||
270deg,
|
rgba(123, 199, 77, 0) 30%,
|
||||||
rgba(123, 199, 77, 0) 30%,
|
#009980,
|
||||||
#009980,
|
rgba(123, 199, 77, 0) 100%);
|
||||||
rgba(123, 199, 77, 0) 100%
|
|
||||||
);
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
animation: ma-bg-move ease-in-out 5s infinite;
|
animation: ma-bg-move ease-in-out 5s infinite;
|
||||||
background-position-x: -500px;
|
background-position-x: -500px;
|
||||||
@@ -103,29 +117,35 @@
|
|||||||
animation-delay: -0.5s;
|
animation-delay: -0.5s;
|
||||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-primary-login.active,
|
.ant-btn-primary-login.active,
|
||||||
.ant-btn-primary-login:active {
|
.ant-btn-primary-login:active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #006655;
|
background-color: #006655;
|
||||||
border-color: #006655;
|
border-color: #006655;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes ma-bg-move {
|
@keyframes ma-bg-move {
|
||||||
0% {
|
0% {
|
||||||
background-position: -500px 0;
|
background-position: -500px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
background-position: 1000px 0;
|
background-position: 1000px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
background-position: 1000px 0;
|
background-position: 1000px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wave-btn-bg {
|
.wave-btn-bg {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.3s cubic-bezier(.645,.045,.355,1);
|
transition: all 0.3s cubic-bezier(.645, .045, .355, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .wave-btn-bg {
|
.dark .wave-btn-bg {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -137,15 +157,21 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.dark .wave-btn-bg:hover {animation: wave-btn-tara 4s ease infinite;}
|
|
||||||
|
.dark .wave-btn-bg:hover {
|
||||||
|
animation: wave-btn-tara 4s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.dark .wave-btn-bg-cl {
|
.dark .wave-btn-bg-cl {
|
||||||
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
|
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
|
||||||
radial-gradient(circle at left top, #006655, #009980, #006655) !important;
|
radial-gradient(circle at left top, #006655, #009980, #006655) !important;
|
||||||
border-radius: 3em;
|
border-radius: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .wave-btn-bg-cl:hover {
|
.dark .wave-btn-bg-cl:hover {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .wave-btn-bg-cl:before {
|
.dark .wave-btn-bg-cl:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: "";
|
content: "";
|
||||||
@@ -160,24 +186,25 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: 0.5s;
|
transition: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .wave-btn-bg-cl:hover::before {
|
.dark .wave-btn-bg-cl:hover::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
filter: blur(20px);
|
filter: blur(20px);
|
||||||
animation: wave-btn-tara 8s linear infinite;
|
animation: wave-btn-tara 8s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes wave-btn-tara {
|
@keyframes wave-btn-tara {
|
||||||
to {
|
to {
|
||||||
background-position: 300%;
|
background-position: 300%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .ant-btn-primary-login {
|
.dark .ant-btn-primary-login {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(rgba(13, 14, 33, 0.45),
|
||||||
rgba(13, 14, 33, 0.45),
|
rgba(13, 14, 33, 0.35));
|
||||||
rgba(13, 14, 33, 0.35)
|
|
||||||
);
|
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -193,6 +220,7 @@
|
|||||||
background-position-x: 0;
|
background-position-x: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.waves-header {
|
.waves-header {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -201,68 +229,83 @@
|
|||||||
color: white;
|
color: white;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .waves-header {
|
.dark .waves-header {
|
||||||
background-color: var(--dark-color-login-background);
|
background-color: var(--dark-color-login-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.waves-inner-header {
|
.waves-inner-header {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.waves {
|
.waves {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
margin-bottom: -8px; /*Fix for safari gap*/
|
margin-bottom: -8px;
|
||||||
|
/*Fix for safari gap*/
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
}
|
}
|
||||||
.parallax > use {
|
|
||||||
|
.parallax>use {
|
||||||
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
||||||
}
|
}
|
||||||
.dark .parallax > use {
|
|
||||||
|
.dark .parallax>use {
|
||||||
fill: var(--dark-color-login-wave);
|
fill: var(--dark-color-login-wave);
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
|
||||||
|
.parallax>use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
animation-duration: 4s;
|
animation-duration: 4s;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(2) {
|
|
||||||
|
.parallax>use:nth-child(2) {
|
||||||
animation-delay: -3s;
|
animation-delay: -3s;
|
||||||
animation-duration: 7s;
|
animation-duration: 7s;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(3) {
|
|
||||||
|
.parallax>use:nth-child(3) {
|
||||||
animation-delay: -4s;
|
animation-delay: -4s;
|
||||||
animation-duration: 10s;
|
animation-duration: 10s;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(4) {
|
|
||||||
animation-delay: -5s;
|
.parallax>use:nth-child(4) {
|
||||||
animation-duration: 13s;
|
animation-delay: -5s;
|
||||||
|
animation-duration: 13s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes move-forever {
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(-90px, 0, 0);
|
transform: translate3d(-90px, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: translate3d(85px, 0, 0);
|
transform: translate3d(85px, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.waves {
|
.waves {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.words-wrapper {
|
.words-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.words-wrapper b {
|
.words-wrapper b {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -270,33 +313,40 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.words-wrapper b.is-visible {
|
.words-wrapper b.is-visible {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline.zoom .words-wrapper {
|
.headline.zoom .words-wrapper {
|
||||||
-webkit-perspective: 300px;
|
-webkit-perspective: 300px;
|
||||||
-moz-perspective: 300px;
|
-moz-perspective: 300px;
|
||||||
perspective: 300px;
|
perspective: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline {
|
.headline {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline.zoom b {
|
.headline.zoom b {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline.zoom b.is-visible {
|
.headline.zoom b.is-visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-animation: zoom-in 0.8s;
|
-webkit-animation: zoom-in 0.8s;
|
||||||
-moz-animation: zoom-in 0.8s;
|
-moz-animation: zoom-in 0.8s;
|
||||||
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-in 0.8s;
|
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-in 0.8s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headline.zoom b.is-hidden {
|
.headline.zoom b.is-hidden {
|
||||||
-webkit-animation: zoom-out 0.8s;
|
-webkit-animation: zoom-out 0.8s;
|
||||||
-moz-animation: zoom-out 0.8s;
|
-moz-animation: zoom-out 0.8s;
|
||||||
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-out 0.4s;
|
animation: cubic-bezier(0.215, 0.610, 0.355, 1.000) zoom-out 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes zoom-in {
|
@-webkit-keyframes zoom-in {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -308,16 +358,19 @@
|
|||||||
-webkit-transform: translateZ(0);
|
-webkit-transform: translateZ(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes zoom-in {
|
@-moz-keyframes zoom-in {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-moz-transform: translateZ(100px);
|
-moz-transform: translateZ(100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-moz-transform: translateZ(0);
|
-moz-transform: translateZ(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes zoom-in {
|
@keyframes zoom-in {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -327,6 +380,7 @@
|
|||||||
-o-transform: translateZ(100px);
|
-o-transform: translateZ(100px);
|
||||||
transform: translateZ(100px);
|
transform: translateZ(100px);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transform: translateZ(0);
|
-webkit-transform: translateZ(0);
|
||||||
@@ -336,26 +390,31 @@
|
|||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes zoom-out {
|
@-webkit-keyframes zoom-out {
|
||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transform: translateZ(0);
|
-webkit-transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: translateZ(-100px);
|
-webkit-transform: translateZ(-100px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes zoom-out {
|
@-moz-keyframes zoom-out {
|
||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-moz-transform: translateZ(0);
|
-moz-transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-moz-transform: translateZ(-100px);
|
-moz-transform: translateZ(-100px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes zoom-out {
|
@keyframes zoom-out {
|
||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -365,6 +424,7 @@
|
|||||||
-o-transform: translateZ(0);
|
-o-transform: translateZ(0);
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transform: translateZ(-100px);
|
-webkit-transform: translateZ(-100px);
|
||||||
@@ -374,21 +434,28 @@
|
|||||||
transform: translateZ(-100px);
|
transform: translateZ(-100px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-menu-item .anticon {
|
|
||||||
margin-right: 4px;
|
.setting-section {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 24px;
|
||||||
}
|
}
|
||||||
.ant-menu-inline .ant-menu-item {
|
|
||||||
padding: 0 16px !important;
|
.setting-section > .ant-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content class="under" style="min-height: 0;">
|
<a-layout-content class="under" :style="{ minHeight: '0' }">
|
||||||
<div class="waves-header">
|
<div class="waves-header">
|
||||||
<div class="waves-inner-header"></div>
|
<div class="waves-inner-header"></div>
|
||||||
<svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
<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">
|
viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto">
|
||||||
<defs>
|
<defs>
|
||||||
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
|
||||||
</defs>
|
</defs>
|
||||||
@@ -400,70 +467,69 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto; overflow-x: hidden;">
|
<div class="setting-section">
|
||||||
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' placement="bottomRight" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<a-space direction="vertical" :size="10">
|
||||||
|
<a-theme-switch-login></a-theme-switch-login>
|
||||||
|
<span>{{ i18n "pages.settings.language" }}</span>
|
||||||
|
<a-select ref="selectLang" :style="{ width: '100%' }" v-model="lang" @change="LanguageManager.setLanguage(lang)" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages">
|
||||||
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
|
<span v-text="l.name"></span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-button shape="circle" icon="setting"></a-button>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
<a-row type="flex" justify="center" align="middle" :style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }">
|
||||||
|
<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">
|
<a-row type="flex" justify="center">
|
||||||
<a-col style="width: 100%;">
|
<a-col :style="{ width: '100%' }">
|
||||||
<h1 class="title headline zoom">
|
<h2 class="title headline zoom">
|
||||||
<span class="words-wrapper">
|
<span class="words-wrapper">
|
||||||
<b class="is-visible">{{ i18n "pages.login.hello" }}</b>
|
<b class="is-visible">{{ i18n "pages.login.hello" }}</b>
|
||||||
<b>{{ i18n "pages.login.title" }}</b>
|
<b>{{ i18n "pages.login.title" }}</b>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h2>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col span="24">
|
<a-col span="24">
|
||||||
<a-form>
|
<a-form>
|
||||||
<a-form-item>
|
<a-space direction="vertical" size="middle">
|
||||||
<a-input autocomplete="username" name="username" v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-form-item>
|
||||||
@keydown.enter.native="login" autofocus>
|
<a-input autocomplete="username" name="username" v-model.trim="user.username"
|
||||||
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
|
placeholder='{{ i18n "username" }}' @keydown.enter.native="login" autofocus>
|
||||||
</a-input>
|
<a-icon slot="prefix" type="user" :style="{ fontSize: '16px' }"></a-icon>
|
||||||
</a-form-item>
|
</a-input>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<a-password-input autocomplete="password" name="password" icon="lock" v-model.trim="user.password"
|
<a-form-item>
|
||||||
placeholder='{{ i18n "password" }}'
|
<a-input-password autocomplete="password" name="password" icon="lock" v-model.trim="user.password"
|
||||||
@keydown.enter.native="login">
|
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||||
</a-password-input>
|
<a-icon slot="prefix" type="lock" :style="{ fontSize: '16px' }"></a-icon>
|
||||||
</a-form-item>
|
</a-input-password>
|
||||||
<a-form-item v-if="secretEnable">
|
</a-form-item>
|
||||||
<a-password-input autocomplete="secret" name="secret" icon="key" v-model.trim="user.loginSecret"
|
<a-form-item v-if="secretEnable">
|
||||||
placeholder='{{ i18n "secretToken" }}'
|
<a-input-password autocomplete="secret" name="secret" icon="lock" v-model.trim="user.loginSecret"
|
||||||
@keydown.enter.native="login">
|
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
||||||
</a-password-input>
|
<a-icon slot="prefix" type="key" :style="{ fontSize: '16px' }"></a-icon>
|
||||||
</a-form-item>
|
</a-input-password>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-form-item>
|
||||||
<div style="height: 50px;" class="wave-btn-bg wave-btn-bg-cl"
|
<a-row justify="center" class="centered">
|
||||||
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
<div :style="{ height: '50px', marginTop: '16px' }" class="wave-btn-bg wave-btn-bg-cl"
|
||||||
<a-button class="ant-btn-primary-login" type="primary"
|
:style="loading ? { width: '52px' } : { display: 'inline-block' }">
|
||||||
:loading="loading" @click="login"
|
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login"
|
||||||
:icon="loading ? 'poweroff' : undefined">
|
:icon="loading ? 'poweroff' : undefined">
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
</a-space>
|
||||||
<a-row justify="center" class="centered">
|
|
||||||
<a-col :span="24">
|
|
||||||
<a-select ref="selectLang" v-model="lang"
|
|
||||||
@change="LanguageManager.setLanguage(lang)" style="width: 200px;"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages">
|
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
|
||||||
<span v-text="l.name"></span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<a-row justify="center" class="centered">
|
|
||||||
<a-theme-switch-login></a-theme-switch-login>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -472,86 +538,84 @@
|
|||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/aThemeSwitch" .}}
|
||||||
{{template "component/password" .}}
|
<script>
|
||||||
<script>
|
const app = new Vue({
|
||||||
class User {
|
delimiters: ['[[', ']]'],
|
||||||
constructor() {
|
el: '#app',
|
||||||
this.username = "";
|
data: {
|
||||||
this.password = "";
|
themeSwitcher,
|
||||||
}
|
loading: false,
|
||||||
}
|
user: {
|
||||||
const app = new Vue({
|
username: "",
|
||||||
delimiters: ['[[', ']]'],
|
password: "",
|
||||||
el: '#app',
|
loginSecret: ""
|
||||||
data: {
|
},
|
||||||
themeSwitcher,
|
secretEnable: false,
|
||||||
loading: false,
|
lang: ""
|
||||||
user: new User(),
|
|
||||||
secretEnable: false,
|
|
||||||
lang: ""
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
this.lang = LanguageManager.getLanguage();
|
|
||||||
this.secretEnable = await this.getSecretStatus();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async login() {
|
|
||||||
this.loading = true;
|
|
||||||
const msg = await HttpUtil.post('/login', this.user);
|
|
||||||
this.loading = false;
|
|
||||||
if (msg.success) {
|
|
||||||
location.href = basePath + 'panel/';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async getSecretStatus() {
|
async mounted() {
|
||||||
this.loading = true;
|
this.lang = LanguageManager.getLanguage();
|
||||||
const msg = await HttpUtil.post('/getSecretStatus');
|
this.secretEnable = await this.getSecretStatus();
|
||||||
this.loading = false;
|
|
||||||
if (msg.success) {
|
|
||||||
this.secretEnable = msg.obj;
|
|
||||||
return msg.obj;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
});
|
async login() {
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
this.loading = true;
|
||||||
var animationDelay = 2000;
|
const msg = await HttpUtil.post('/login', this.user);
|
||||||
initHeadline();
|
this.loading = false;
|
||||||
|
if (msg.success) {
|
||||||
|
location.href = basePath + 'panel/';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getSecretStatus() {
|
||||||
|
this.loading = true;
|
||||||
|
const msg = await HttpUtil.post('/getSecretStatus');
|
||||||
|
this.loading = false;
|
||||||
|
if (msg.success) {
|
||||||
|
this.secretEnable = msg.obj;
|
||||||
|
return msg.obj;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function initHeadline() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
animateHeadline(document.querySelectorAll('.headline'));
|
var animationDelay = 2000;
|
||||||
}
|
initHeadline();
|
||||||
|
|
||||||
function animateHeadline(headlines) {
|
function initHeadline() {
|
||||||
var duration = animationDelay;
|
animateHeadline(document.querySelectorAll('.headline'));
|
||||||
headlines.forEach(function(headline) {
|
}
|
||||||
setTimeout(function() {
|
|
||||||
hideWord(headline.querySelector('.is-visible'));
|
|
||||||
}, duration);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideWord(word) {
|
function animateHeadline(headlines) {
|
||||||
var nextWord = takeNext(word);
|
var duration = animationDelay;
|
||||||
switchWord(word, nextWord);
|
headlines.forEach(function (headline) {
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
hideWord(nextWord);
|
hideWord(headline.querySelector('.is-visible'));
|
||||||
}, animationDelay);
|
}, duration);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function takeNext(word) {
|
function hideWord(word) {
|
||||||
return (word.nextElementSibling) ? word.nextElementSibling : word.parentElement.firstElementChild;
|
var nextWord = takeNext(word);
|
||||||
}
|
switchWord(word, nextWord);
|
||||||
|
setTimeout(function () {
|
||||||
|
hideWord(nextWord);
|
||||||
|
}, animationDelay);
|
||||||
|
}
|
||||||
|
|
||||||
function switchWord(oldWord, newWord) {
|
function takeNext(word) {
|
||||||
oldWord.classList.remove('is-visible');
|
return word.nextElementSibling ? word.nextElementSibling : word.parentElement.firstElementChild;
|
||||||
oldWord.classList.add('is-hidden');
|
}
|
||||||
newWord.classList.remove('is-hidden');
|
|
||||||
newWord.classList.add('is-visible');
|
function switchWord(oldWord, newWord) {
|
||||||
}
|
oldWord.classList.remove('is-visible');
|
||||||
});
|
oldWord.classList.add('is-hidden');
|
||||||
</script>
|
newWord.classList.remove('is-hidden');
|
||||||
|
newWord.classList.add('is-visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "clientsBulkModal"}}
|
{{define "modals/clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title"
|
<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="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input-number style="width: 50%" v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
|
<a-input-number :style="{ width: '50%' }" v-model.number="clientsBulkModal.tgId" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="app.ipLimitEnable">
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{{define "clientsModal"}}
|
{{define "modals/clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
<template v-if="isEdit">
|
<template v-if="isEdit">
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" :style="{ marginBottom: '10px', display: 'block', textAlign: 'center' }">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
</template>
|
</template>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "dnsModal"}}
|
{{define "modals/dnsModal"}}
|
||||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true"
|
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true"
|
||||||
:mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}'
|
:mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||||
<a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%"
|
<a-select v-model="dnsModal.dnsServer.queryStrategy" :style="{ width: '100%' }"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "fakednsModal"}}
|
{{define "modals/fakednsModal"}}
|
||||||
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
||||||
:closable="true" :mask-closable="false"
|
:closable="true" :mask-closable="false"
|
||||||
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "inboundInfoModal"}}
|
{{define "modals/inboundInfoModal"}}
|
||||||
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true" :mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
|
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' :closable="true" :mask-closable="true" :footer="null" width="600px" :class="themeSwitcher.currentTheme">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :md="12">
|
<a-col :xs="24" :md="12">
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
<a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
|
<a-tag v-else color="orange">{{ i18n "none" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSS" :style="{ marginBottom: '10px', width: '100%' }">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<table style="margin-bottom: 10px;">
|
<table :style="{ marginBottom: '10px' }">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "pages.inbounds.email" }}</td>
|
<td>{{ i18n "pages.inbounds.email" }}</td>
|
||||||
<td v-if="infoModal.clientSettings.email">
|
<td v-if="infoModal.clientSettings.email">
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
||||||
<a-icon type="sync" :spin="refreshing" @click="refreshIPs" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="refreshIPs" :style="{ margin: '0 5px' }"></a-icon>
|
||||||
<a-tooltip :title="[[ dbInbound.address ]]">
|
<a-tooltip :title="[[ dbInbound.address ]]">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table style="display: inline-table; margin-block: 10px; width: 100%; text-align: center;">
|
<table :style="{ display: 'inline-table', marginBlock: '10px', width: '100%', textAlign: 'center' }">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "remained" }}</th>
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
<tr-info-title class="tr-info-title">
|
<tr-info-title class="tr-info-title">
|
||||||
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</tr-info-title>
|
</tr-info-title>
|
||||||
<code>[[ link.link ]]</code>
|
<code>[[ link.link ]]</code>
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
<tr-info-title class="tr-info-title">
|
<tr-info-title class="tr-info-title">
|
||||||
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</tr-info-title>
|
</tr-info-title>
|
||||||
<code>[[ link.link ]]</code>
|
<code>[[ link.link ]]</code>
|
||||||
@@ -431,10 +431,10 @@
|
|||||||
<tr-info-title class="tr-info-title">
|
<tr-info-title class="tr-info-title">
|
||||||
<a-tag color="blue">Config</a-tag>
|
<a-tag color="blue">Config</a-tag>
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(infoModal.links[index])"></a-button>
|
<a-button :style="{ minWidth: '24px' }" size="small" icon="snippets" @click="copy(infoModal.links[index])"></a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</tr-info-title>
|
</tr-info-title>
|
||||||
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row">
|
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" :style="{ borderRadius: '1rem', padding: '0.5rem' }" class="client-table-odd-row">
|
||||||
</div>
|
</div>
|
||||||
</tr-info-row>
|
</tr-info-row>
|
||||||
</td>
|
</td>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "inboundModal"}}
|
{{define "modals/inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
|
||||||
:dialog-style="{ top: '20px' }" @ok="inModal.ok"
|
:dialog-style="{ top: '20px' }" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "promptModal"}}
|
{{define "modals/promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
:confirm-loading="promptModal.confirmLoading"
|
:confirm-loading="promptModal.confirmLoading"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "qrcodeModal"}}
|
{{define "modals/qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:dialog-style="isMobileQr ? { top: '18px' } : {}"
|
:dialog-style="isMobile ? { top: '18px' } : {}"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
:footer="null" width="fit-content">
|
:footer="null" width="fit-content">
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const isMobileQr = window.innerWidth <= 768;
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
@@ -74,6 +73,7 @@
|
|||||||
const qrModalApp = new Vue({
|
const qrModalApp = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#qrcode-modal',
|
el: '#qrcode-modal',
|
||||||
|
mixins: [MediaQueryMixin],
|
||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
{{define "textModal"}}
|
{{define "modals/textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title" :closable="true"
|
||||||
:closable="true"
|
:class="themeSwitcher.currentTheme">
|
||||||
:class="themeSwitcher.currentTheme">
|
<a-input :style="{ overflowY: 'auto' }" type="textarea" v-model="txtModal.content"
|
||||||
|
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||||
<template slot="footer">
|
<template slot="footer">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
@click="FileManager.downloadTextFile(txtModal.content, txtModal.fileName)">
|
||||||
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
<span>[[ txtModal.fileName ]]</span>
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" icon="copy" @click="txtModal.copy(txtModal.content)">
|
||||||
|
<span>{{ i18n "copy" }}</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" @click="txtModal.copy(txtModal.content)">{{ i18n "copy" }}</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
|
|
||||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const txtModal = {
|
const txtModal = {
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: '',
|
||||||
@@ -47,6 +47,5 @@
|
|||||||
txtModal: txtModal,
|
txtModal: txtModal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "warpModal"}}
|
{{define "modals/warpModal"}}
|
||||||
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
|
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
|
||||||
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
|
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
|
||||||
:footer="null" :class="themeSwitcher.currentTheme">
|
:footer="null" :class="themeSwitcher.currentTheme">
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.create" }}</a-button>
|
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.create" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<table style="margin: 5px 0; width: 100%;">
|
<table :style="{ margin: '5px 0', width: '100%' }">
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
<td>Access Token</td>
|
<td>Access Token</td>
|
||||||
<td>[[ warpModal.warpData.access_token ]]</td>
|
<td>[[ warpModal.warpData.access_token ]]</td>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<a-button @click="delConfig" :loading="warpModal.confirmLoading" type="danger">{{ i18n "delete" }}</a-button>
|
<a-button @click="delConfig" :loading="warpModal.confirmLoading" type="danger">{{ i18n "delete" }}</a-button>
|
||||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||||
<a-collapse style="margin: 10px 0;">
|
<a-collapse :style="{ margin: '10px 0' }">
|
||||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Key">
|
<a-form-item label="Key">
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
|
<a-divider :style="{ margin: '0' }">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
|
||||||
<a-button icon="sync" @click="getConfig" style="margin-top: 5px; margin-bottom: 10px;"
|
<a-button icon="sync" @click="getConfig" :style="{ marginTop: '5px', marginBottom: '10px' }"
|
||||||
:loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
|
:loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
|
||||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
|
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
|
||||||
<table style="width: 100%">
|
<table :style="{ width: '100%' }">
|
||||||
<tr class="client-table-odd-row">
|
<tr class="client-table-odd-row">
|
||||||
<td>Device Name</td>
|
<td>Device Name</td>
|
||||||
<td>[[ warpModal.warpConfig.name ]]</td>
|
<td>[[ warpModal.warpConfig.name ]]</td>
|
||||||
@@ -77,14 +77,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
<a-divider style="margin: 10px 0;">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
|
<a-divider :style="{ margin: '10px 0' }">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
|
||||||
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<template v-if="warpOutboundIndex>=0">
|
<template v-if="warpOutboundIndex>=0">
|
||||||
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
|
<a-tag color="green" :style="{ lineHeight: '31px' }">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
|
<a-tag color="orange" :style="{ lineHeight: '31px' }">{{ i18n "disabled" }}</a-tag>
|
||||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -176,10 +176,10 @@
|
|||||||
},
|
},
|
||||||
async register() {
|
async register() {
|
||||||
warpModal.loading(true);
|
warpModal.loading(true);
|
||||||
keys = Wireguard.generateKeypair();
|
const keys = Wireguard.generateKeypair();
|
||||||
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
|
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
resp = JSON.parse(msg.obj);
|
const resp = JSON.parse(msg.obj);
|
||||||
warpModal.warpData = resp.data;
|
warpModal.warpData = resp.data;
|
||||||
warpModal.warpConfig = resp.config;
|
warpModal.warpConfig = resp.config;
|
||||||
this.collectConfig();
|
this.collectConfig();
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "balancerModal"}}
|
{{define "modals/balancerModal"}}
|
||||||
<a-modal
|
<a-modal
|
||||||
id="balancer-modal"
|
id="balancer-modal"
|
||||||
v-model="balancerModal.visible"
|
v-model="balancerModal.visible"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "outModal"}}
|
{{define "modals/outModal"}}
|
||||||
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
||||||
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-button-props="{ props: { disabled: !outModal.isValid } }" style="overflow: hidden;"
|
:ok-button-props="{ props: { disabled: !outModal.isValid } }" :style="{ overflow: 'hidden' }"
|
||||||
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
{{template "form/outbound"}}
|
{{template "form/outbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "reverseModal"}}
|
{{define "modals/reverseModal"}}
|
||||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{define "ruleModal"}}
|
{{define "modals/ruleModal"}}
|
||||||
<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-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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='Domain Matcher'>
|
<a-form-item label='Domain Matcher'>
|
||||||
@@ -37,14 +37,14 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Attributes'>
|
<a-form-item label='Attributes'>
|
||||||
<a-button icon="plus" size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
|
<a-button icon="plus" size="small" :style="{ marginLeft: '10px' }" @click="ruleModal.rule.attrs.push(['', ''])"></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span: 24}">
|
<a-form-item :wrapper-col="{span: 24}">
|
||||||
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
<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" }}'>
|
<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>
|
<template slot="addonBefore" :style="{ margin: '0' }">[[ index+1 ]]</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
<a-input :style="{ width: '50%' }" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-button icon="minus" slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)"></a-button>
|
<a-button icon="minus" slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)"></a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
545
web/html/settings.html
Normal file
545
web/html/settings.html
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{template "head" .}}
|
||||||
|
<style>
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.ant-layout-content {
|
||||||
|
margin: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-tabs-nav .ant-tabs-tab {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-tabs-bar {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.ant-list-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.alert-msg {
|
||||||
|
color: rgb(194, 117, 18);
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(255 145 0 / 15%);
|
||||||
|
margin: 1.5rem 2.5rem 0rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
transition: all 0.5s;
|
||||||
|
animation: signal 3s cubic-bezier(0.18, 0.89, 0.32, 1.28) infinite;
|
||||||
|
}
|
||||||
|
.alert-msg:hover {
|
||||||
|
cursor: default;
|
||||||
|
transition-duration: .3s;
|
||||||
|
animation: signal 0.9s ease infinite;
|
||||||
|
}
|
||||||
|
@keyframes signal {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(194, 118, 18, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert-msg>i {
|
||||||
|
color: inherit;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.dark .ant-input-password-icon {
|
||||||
|
color: var(--dark-color-text-primary);
|
||||||
|
}
|
||||||
|
.ant-collapse-content-box .ant-alert {
|
||||||
|
margin-block-end: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
|
<a-sidebar></a-sidebar>
|
||||||
|
<a-layout id="content-layout">
|
||||||
|
<a-layout-content>
|
||||||
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="confAlerts.length>0" :style="{ marginBottom: '10px' }"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
show-icon closable>
|
||||||
|
<template slot="description">
|
||||||
|
<b>{{ i18n "secAlertConf" }}</b>
|
||||||
|
<ul><li v-for="a in confAlerts">[[ a ]]</li></ul>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-card hoverable :style="{ marginBottom: '.5rem', overflowX: 'hidden' }">
|
||||||
|
<a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
|
||||||
|
<a-col :xs="24" :sm="10" :style="{ padding: '4px' }">
|
||||||
|
<a-space direction="horizontal">
|
||||||
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="14">
|
||||||
|
<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>
|
||||||
|
</a-alert>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
<a-tabs default-active-key="1">
|
||||||
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' :style="{ paddingTop: '20px' }">
|
||||||
|
{{ template "settings/panel/general" . }}
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' :style="{ paddingTop: '20px' }">
|
||||||
|
{{ template "settings/panel/security" . }}
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' :style="{ paddingTop: '20px' }">
|
||||||
|
{{ template "settings/panel/telegram" . }}
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' :style="{ paddingTop: '20px' }">
|
||||||
|
{{ template "settings/panel/subscription/general" . }}
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
|
||||||
|
{{ template "settings/panel/subscription/json" . }}
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-space>
|
||||||
|
</a-spin>
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||||
|
{{template "component/aSidebar" .}}
|
||||||
|
{{template "component/aThemeSwitch" .}}
|
||||||
|
{{template "component/aSettingListItem" .}}
|
||||||
|
<script>
|
||||||
|
const app = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
themeSwitcher,
|
||||||
|
spinning: false,
|
||||||
|
changeSecret: false,
|
||||||
|
oldAllSetting: new AllSetting(),
|
||||||
|
allSetting: new AllSetting(),
|
||||||
|
saveBtnDisable: true,
|
||||||
|
user: {},
|
||||||
|
lang: LanguageManager.getLanguage(),
|
||||||
|
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
||||||
|
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||||
|
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
||||||
|
remarkSample: '',
|
||||||
|
defaultFragment: {
|
||||||
|
tag: "fragment",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "AsIs",
|
||||||
|
fragment: {
|
||||||
|
packets: "tlshello",
|
||||||
|
length: "100-200",
|
||||||
|
interval: "10-20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
streamSettings: {
|
||||||
|
sockopt: {
|
||||||
|
tcpKeepAliveIdle: 100,
|
||||||
|
tcpMptcp: true,
|
||||||
|
penetrate: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultNoises: {
|
||||||
|
tag: "noises",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "AsIs",
|
||||||
|
noises: [
|
||||||
|
{ type: "rand", packet: "10-20", delay: "10-16" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultMux: {
|
||||||
|
enabled: true,
|
||||||
|
concurrency: 8,
|
||||||
|
xudpConcurrency: 16,
|
||||||
|
xudpProxyUDP443: "reject"
|
||||||
|
},
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
domain: [
|
||||||
|
"geosite:category-ir"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
ip: [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
directIPsOptions: [
|
||||||
|
{ label: 'Private IP', value: 'geoip:private' },
|
||||||
|
{ label: '🇮🇷 Iran', value: 'geoip:ir' },
|
||||||
|
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||||
|
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||||
|
{ label: '🇻🇳 Vietnam', value: 'geoip:vn' },
|
||||||
|
{ label: '🇪🇸 Spain', value: 'geoip:es' },
|
||||||
|
{ label: '🇮🇩 Indonesia', value: 'geoip:id' },
|
||||||
|
{ label: '🇺🇦 Ukraine', value: 'geoip:ua' },
|
||||||
|
{ label: '🇹🇷 Türkiye', value: 'geoip:tr' },
|
||||||
|
{ label: '🇧🇷 Brazil', value: 'geoip:br' },
|
||||||
|
],
|
||||||
|
diretDomainsOptions: [
|
||||||
|
{ label: 'Private DNS', value: 'geosite:private' },
|
||||||
|
{ label: '🇮🇷 Iran', value: 'geosite:category-ir' },
|
||||||
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
|
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||||
|
{ label: 'Apple', value: 'geosite:apple' },
|
||||||
|
{ label: 'Meta', value: 'geosite:meta' },
|
||||||
|
{ label: 'Google', value: 'geosite:google' },
|
||||||
|
],
|
||||||
|
get remarkModel() {
|
||||||
|
rm = this.allSetting.remarkModel;
|
||||||
|
return rm.length > 1 ? rm.substring(1).split('') : [];
|
||||||
|
},
|
||||||
|
set remarkModel(value) {
|
||||||
|
rs = this.allSetting.remarkModel[0];
|
||||||
|
this.allSetting.remarkModel = rs + value.join('');
|
||||||
|
this.changeRemarkSample();
|
||||||
|
},
|
||||||
|
get remarkSeparator() {
|
||||||
|
return this.allSetting.remarkModel.length > 1 ? this.allSetting.remarkModel.charAt(0) : '-';
|
||||||
|
},
|
||||||
|
set remarkSeparator(value) {
|
||||||
|
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]));
|
||||||
|
this.remarkSample = sample.length == 0 ? '' : sample.join(this.remarkSeparator);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loading(spinning = true) {
|
||||||
|
this.spinning = spinning;
|
||||||
|
},
|
||||||
|
async getAllSetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/all");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.oldAllSetting = new AllSetting(msg.obj);
|
||||||
|
this.allSetting = new AllSetting(msg.obj);
|
||||||
|
app.changeRemarkSample();
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
await this.fetchUserSecret();
|
||||||
|
},
|
||||||
|
async updateAllSetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
await this.getAllSetting();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateUser() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.user = {};
|
||||||
|
window.location.replace(basePath + "logout");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartPanel() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => resolve(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
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;
|
||||||
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
|
const url = URLBuilder.buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||||
|
window.location.replace(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchUserSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
||||||
|
if (userMessage.success) {
|
||||||
|
this.user = userMessage.obj;
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
async updateSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||||
|
if (msg && msg.obj) {
|
||||||
|
this.user = msg.obj;
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
await this.updateAllSetting();
|
||||||
|
},
|
||||||
|
generateRandomString(length) {
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
|
let randomString = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
},
|
||||||
|
async getNewSecret() {
|
||||||
|
if (!this.changeSecret) {
|
||||||
|
this.changeSecret = true;
|
||||||
|
this.user.loginSecret = '';
|
||||||
|
const newSecret = this.generateRandomString(64);
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.user.loginSecret = newSecret;
|
||||||
|
this.changeSecret = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async toggleToken(value) {
|
||||||
|
if (value) {
|
||||||
|
await this.getNewSecret();
|
||||||
|
} else {
|
||||||
|
this.user.loginSecret = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addNoise() {
|
||||||
|
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
||||||
|
this.noisesArray = [...this.noisesArray, newNoise];
|
||||||
|
},
|
||||||
|
removeNoise(index) {
|
||||||
|
const newNoises = [...this.noisesArray];
|
||||||
|
newNoises.splice(index, 1);
|
||||||
|
this.noisesArray = newNoises;
|
||||||
|
},
|
||||||
|
updateNoiseType(index, value) {
|
||||||
|
const updatedNoises = [...this.noisesArray];
|
||||||
|
updatedNoises[index] = { ...updatedNoises[index], type: value };
|
||||||
|
this.noisesArray = updatedNoises;
|
||||||
|
},
|
||||||
|
updateNoisePacket(index, value) {
|
||||||
|
const updatedNoises = [...this.noisesArray];
|
||||||
|
updatedNoises[index] = { ...updatedNoises[index], packet: value };
|
||||||
|
this.noisesArray = updatedNoises;
|
||||||
|
},
|
||||||
|
updateNoiseDelay(index, value) {
|
||||||
|
const updatedNoises = [...this.noisesArray];
|
||||||
|
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
||||||
|
this.noisesArray = updatedNoises;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fragment: {
|
||||||
|
get: function () { return this.allSetting?.subJsonFragment != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentPackets: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.packets = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentLength: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.length = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentInterval: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.interval = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noises: {
|
||||||
|
get() {
|
||||||
|
return this.allSetting?.subJsonNoises != "";
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
if (v) {
|
||||||
|
this.allSetting.subJsonNoises = JSON.stringify(this.defaultNoises);
|
||||||
|
} else {
|
||||||
|
this.allSetting.subJsonNoises = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noisesArray: {
|
||||||
|
get() {
|
||||||
|
return this.noises ? JSON.parse(this.allSetting.subJsonNoises).settings.noises : [];
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
if (this.noises) {
|
||||||
|
const newNoises = JSON.parse(this.allSetting.subJsonNoises);
|
||||||
|
newNoises.settings.noises = value;
|
||||||
|
this.allSetting.subJsonNoises = JSON.stringify(newNoises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableMux: {
|
||||||
|
get: function () { return this.allSetting?.subJsonMux != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxConcurrency: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.concurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpConcurrency: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpConcurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpProxyUDP443: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpProxyUDP443 = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableDirect: {
|
||||||
|
get: function () { return this.allSetting?.subJsonRules != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directIPs: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.enableDirect) return [];
|
||||||
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return [];
|
||||||
|
const ipRule = rules.find(r => r.ip);
|
||||||
|
return ipRule?.ip ?? [];
|
||||||
|
},
|
||||||
|
set: function (v) {
|
||||||
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return;
|
||||||
|
|
||||||
|
if (v.length == 0) {
|
||||||
|
rules = rules.filter(r => !r.ip);
|
||||||
|
} else {
|
||||||
|
let ruleIndex = rules.findIndex(r => r.ip);
|
||||||
|
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[1]) - 1;
|
||||||
|
|
||||||
|
rules[ruleIndex].ip = [];
|
||||||
|
v.forEach(d => {
|
||||||
|
rules[ruleIndex].ip.push(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directDomains: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.enableDirect) return [];
|
||||||
|
const rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return [];
|
||||||
|
const domainRule = rules.find(r => r.domain);
|
||||||
|
return domainRule?.domain ?? [];
|
||||||
|
},
|
||||||
|
set: function (v) {
|
||||||
|
let rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return;
|
||||||
|
if (v.length == 0) {
|
||||||
|
rules = rules.filter(r => !r.domain);
|
||||||
|
} else {
|
||||||
|
let ruleIndex = rules.findIndex(r => r.domain);
|
||||||
|
if (ruleIndex == -1) ruleIndex = rules.push(this.defaultRules[0]) - 1;
|
||||||
|
|
||||||
|
rules[ruleIndex].domain = v;
|
||||||
|
}
|
||||||
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confAlerts: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.allSetting) return [];
|
||||||
|
var alerts = []
|
||||||
|
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||||
|
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||||
|
panelPath = window.location.pathname.split('/').length < 4
|
||||||
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||||
|
if (this.allSetting.subEnable) {
|
||||||
|
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||||
|
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||||
|
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||||
|
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||||
|
}
|
||||||
|
return alerts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.getAllSetting();
|
||||||
|
while (true) {
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
149
web/html/settings/panel/general.html
Normal file
149
web/html/settings/panel/general.html
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{{define "settings/panel/general"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>
|
||||||
|
{{ i18n "pages.settings.remarkModel"}}
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i>
|
||||||
|
</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-group :style="{ width: '100%' }">
|
||||||
|
<a-select :style="{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }" mode="multiple"
|
||||||
|
v-model="remarkModel" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="(value, key) in remarkModels" :value="key">[[ value ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select :style="{ width: '20%' }" v-model="remarkSeparator"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.panelListeningIP"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.panelListeningIPDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.webListen"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.panelListeningDomain"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.panelListeningDomainDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.webDomain"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.panelPort"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="1" :min="65531" v-model="allSetting.webPort" :style="{ width: '100%' }"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.panelUrlPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.panelUrlPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.webBasePath"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.sessionMaxAge" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.sessionMaxAgeDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="60" v-model="allSetting.sessionMaxAge" :style="{ width: '100%' }"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.pageSize" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.pageSizeDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="0" step="5" v-model="allSetting.pageSize" :style="{ width: '100%' }"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.language"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select ref="selectLang" v-model="lang" @change="LanguageManager.setLanguage(lang)"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||||
|
<a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
|
||||||
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span
|
||||||
|
v-text="l.name"></span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.notifications" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.expireTimeDiff" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.expireTimeDiffDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="0" v-model="allSetting.expireDiff" :style="{ width: '100%' }"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.trafficDiff" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.trafficDiffDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="0" v-model="allSetting.trafficDiff" :style="{ width: '100%' }"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header='{{ i18n "pages.settings.certs" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.publicKeyPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.publicKeyPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.webCertFile"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.privateKeyPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.privateKeyPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.webKeyFile"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="4" header='{{ i18n "pages.settings.externalTraffic" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.externalTrafficInformEnable"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.externalTrafficInformEnableDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.externalTrafficInformEnable"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.externalTrafficInformURI"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.externalTrafficInformURIDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder="(http|https)://domain[:port]/path/"
|
||||||
|
v-model="allSetting.externalTrafficInformURI"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="5" header='{{ i18n "pages.settings.dateAndTime" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.timeZone"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.timeZoneDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.timeLocation"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.datepicker"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.datepickerDescription"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select :style="{ width: '100%' }" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker">
|
||||||
|
<a-select-option v-for="item in datepickerList" :value="item.value">
|
||||||
|
<span v-text="item.name"></span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
60
web/html/settings/panel/security.html
Normal file
60
web/html/settings/panel/security.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{{define "settings/panel/security"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.settings.security.admin"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.oldUsername"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input autocomplete="username" v-model="user.oldUsername"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.currentPassword"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-password autocomplete="current-password" v-model="user.oldPassword"></a-input-password>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.newUsername"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input v-model="user.newUsername"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.newPassword"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-password autocomplete="new-password" v-model="user.newPassword"></a-input-password>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||||
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.security.secret"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
||||||
|
<a-icon :style="{ marginLeft: '1rem' }" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync"
|
||||||
|
@click="getNewSecret"></a-icon>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item>
|
||||||
|
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||||
|
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">
|
||||||
|
<span>{{ i18n "confirm"}}</span>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
98
web/html/settings/panel/subscription/general.html
Normal file
98
web/html/settings/panel/subscription/general.html
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{{define "settings/panel/subscription/general"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subEnable"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subEnableDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.subEnable"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subTitle"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subTitle"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subListen"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subListen"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subDomain"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subDomainDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subDomain"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number v-model="allSetting.subPort" :min="1" :min="65531"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subPath"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subURI"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder="(http|https)://domain[:port]/path/"
|
||||||
|
v-model="allSetting.subURI"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.information" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subEncrypt"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subEncryptDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.subEncrypt"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subShowInfo"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subShowInfoDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.subShowInfo"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header='{{ i18n "pages.settings.certs" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subCertPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subCertPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subCertFile"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subKeyPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subKeyPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subKeyFile"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="4" header='{{ i18n "pages.settings.intervals"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subUpdates"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subUpdatesDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="1" v-model="allSetting.subUpdates" :style="{ width: '100%' }"></a-input-number>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
180
web/html/settings/panel/subscription/json.html
Normal file
180
web/html/settings/panel/subscription/json.html
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
{{define "settings/panel/subscription/json"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.subJsonPath"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.subURI"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder="(http|https)://domain[:port]/path/"
|
||||||
|
v-model="allSetting.subJsonURI"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.fragment"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.fragment"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.fragmentDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="fragment"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item v-if="fragment" :style="{ padding: '10px 20px' }">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment">
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Packets</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="fragmentPackets"
|
||||||
|
placeholder="1-1 | 1-3 | tlshello | ..."></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Length</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="fragmentLength" placeholder="100-200"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Interval</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="fragmentInterval" placeholder="10-20"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header="Noises">
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Noises</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.noisesDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="noises"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item v-if="noises" :style="{ padding: '10px 20px' }">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise №${index + 1}`">
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Type</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select :value="noise.type" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="(value) => updateNoiseType(index, value)">
|
||||||
|
<a-select-option :value="p" :label="p" v-for="p in ['rand', 'base64', 'str', 'hex']" :key="p">
|
||||||
|
<span>[[ p ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Packet</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" :value="noise.packet"
|
||||||
|
@input="(value) => updateNoisePacket(index, event.target.value)"
|
||||||
|
placeholder="5-10"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Delay (ms)</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" :value="noise.delay"
|
||||||
|
@input="(value) => updateNoiseDelay(index, event.target.value)"
|
||||||
|
placeholder="10-20"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-space direction="horizontal" :style="{ padding: '10px 20px' }">
|
||||||
|
<a-button v-if="noisesArray.length > 1" type="danger"
|
||||||
|
@click="removeNoise(index)">Remove</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-button v-if="noises" type="primary" @click="addNoise" :style="{ marginTop: '10px' }">Add Noise</a-button>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="4" header='{{ i18n "pages.settings.mux"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.mux"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.muxDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="enableMux"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item v-if="enableMux" :style="{ padding: '10px 20px' }">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>Concurrency</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number v-model="muxConcurrency" :min="-1" :max="1024"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>xudp Concurrency</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number v-model="muxXudpConcurrency" :min="-1" :max="1024"
|
||||||
|
:style="{ width: '100%' }"></a-input-number>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>xudp UDP 443</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="muxXudpProxyUDP443" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']">
|
||||||
|
<span>[[ p ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="5" header='{{ i18n "pages.settings.direct" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.direct"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.directDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="enableDirect"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-list-item v-if="enableDirect" :style="{ padding: '10px 20px' }">
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directIPs"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in directIPsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directDomains"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in diretDomainsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
87
web/html/settings/panel/telegram.html
Normal file
87
web/html/settings/panel/telegram.html
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{{define "settings/panel/telegram"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramBotEnable" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramBotEnableDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.tgBotEnable"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramToken"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramTokenDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.tgBotToken"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramChatId"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramChatIdDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.tgBotChatId"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramBotLanguage"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select ref="selectBotLang" v-model="allSetting.tgLang"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||||
|
<a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
|
||||||
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span
|
||||||
|
v-text="l.name"></span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.settings.notifications" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramNotifyTime"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramNotifyTimeDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="allSetting.tgRunTime"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.tgNotifyBackup" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.tgNotifyBackupDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.tgBotBackup"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.tgNotifyLogin" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.tgNotifyLoginDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="allSetting.tgBotLoginNotify"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.tgNotifyCpu" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.tgNotifyCpuDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input-number :min="0" :min="100" v-model="allSetting.tgCpu" :style="{ width: '100%' }"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header='{{ i18n "pages.settings.proxyAndServer" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramProxy"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramProxyDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder="socks5://user:pass@host:port"
|
||||||
|
v-model="allSetting.tgBotProxy"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.settings.telegramAPIServer"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.settings.telegramAPIServerDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" placeholder="https://api.example.com"
|
||||||
|
v-model="allSetting.tgBotAPIServer"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
14
web/html/settings/xray/advanced.html
Normal file
14
web/html/settings/xray/advanced.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{{define "settings/xray/advanced"}}
|
||||||
|
<a-space direction="vertical" size="small">
|
||||||
|
<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>
|
||||||
|
<a-radio-button value="inboundSettings">{{ i18n "pages.xray.Inbounds" }}</a-radio-button>
|
||||||
|
<a-radio-button value="outboundSettings">{{ i18n "pages.xray.Outbounds" }}</a-radio-button>
|
||||||
|
<a-radio-button value="routingRuleSettings">{{ i18n "pages.xray.Routings" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<textarea :style="{ position: 'absolute', left: '-800px' }" id="xraySetting"></textarea>
|
||||||
|
</a-space>
|
||||||
|
{{end}}
|
||||||
53
web/html/settings/xray/balancers.html
Normal file
53
web/html/settings/xray/balancers.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{{define "settings/xray/balancers"}}
|
||||||
|
<template v-if="balancersData.length > 0">
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-button type="primary" icon="plus" @click="addBalancer()">
|
||||||
|
<span>{{ i18n "pages.xray.balancer.addBalancer"}}</span>
|
||||||
|
</a-button>
|
||||||
|
<a-table :columns="balancerColumns" bordered :row-key="r => r.key" :data-source="balancersData"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
||||||
|
<template slot="action" slot-scope="text, balancer, index">
|
||||||
|
<span>[[ index+1 ]]</span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editBalancer(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
<span>{{ i18n "edit" }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteBalancer(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon>
|
||||||
|
<span>{{ i18n "delete"}}</span>
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="strategy" slot-scope="text, balancer, index">
|
||||||
|
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
|
||||||
|
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
|
||||||
|
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='leastLoad'" color="green">Least Load</a-tag>
|
||||||
|
<a-tag :style="{ margin: '0' }" v-if="balancer.strategy=='leastPing'" color="green">Least Ping</a-tag>
|
||||||
|
</template>
|
||||||
|
<template slot="selector" slot-scope="text, balancer, index">
|
||||||
|
<a-tag class="info-large-tag" :style="{ margin: '1' }" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-radio-group v-if="observatoryEnable || burstObservatoryEnable" v-model="obsSettings" @change="changeObsCode"
|
||||||
|
button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
|
<a-radio-button value="observatory" v-if="observatoryEnable">Observatory</a-radio-button>
|
||||||
|
<a-radio-button value="burstObservatory" v-if="burstObservatoryEnable">Burst Observatory</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<textarea :style="{ position: 'absolute', left: '-800px' }" id="obsSetting"></textarea>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-empty description='{{ i18n "emptyBalancersDesc" }}' :style="{ margin: '10px' }">
|
||||||
|
<a-button type="primary" icon="plus" @click="addBalancer()" :style="{ marginTop: '10px' }">
|
||||||
|
<span>{{ i18n "pages.xray.balancer.addBalancer"}}</span>
|
||||||
|
</a-button>
|
||||||
|
</a-empty>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
275
web/html/settings/xray/basics.html
Normal file
275
web/html/settings/xray/basics.html
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
{{define "settings/xray/basics"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.generalConfigsDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.FreedomStrategy" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
:style="{ width: '100%' }">
|
||||||
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.RoutingStrategy" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
:style="{ width: '100%' }">
|
||||||
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="2" header='{{ i18n "pages.xray.statistics" }}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.statsInboundUplink" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.statsInboundUplinkDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="statsInboundUplink"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.statsInboundDownlink" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.statsInboundDownlinkDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="statsInboundDownlink"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.statsOutboundUplink" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.statsOutboundUplinkDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="statsOutboundUplink"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.statsOutboundDownlink" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.statsOutboundDownlinkDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="statsOutboundDownlink"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header='{{ i18n "pages.xray.logConfigs" }}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.logConfigsDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.logLevel" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.logLevelDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||||
|
<a-select-option v-for="s in log.loglevel" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.accessLog" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.accessLogDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||||
|
<a-select-option value=''>
|
||||||
|
<span>Empty</span>
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option v-for="s in log.access" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.errorLog" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.errorLogDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" :style="{ width: '100%' }">
|
||||||
|
<a-select-option value=''>
|
||||||
|
<span>Empty</span>
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option v-for="s in log.error" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.maskAddress" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.maskAddressDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
:style="{ width: '100%' }">
|
||||||
|
<a-select-option value=''>
|
||||||
|
<span>Empty</span>
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option v-for="s in log.maskAddress" :value="s">
|
||||||
|
<span>[[ s ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dnsLog"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dnsLogDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="dnslog"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="4" header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.blockConfigsDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.Torrent"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.TorrentDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="torrentSettings"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.Family"}}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.FamilyDesc"}}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="familyProtectSettings"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="5" header='{{ i18n "pages.xray.basicRouting"}}'>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.blockips" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" v-model="blockedIPs" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.blockdomains" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" v-model="blockedDomains" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.BlockDomainsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.directConnectionsConfigsDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directIPs"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="directDomains"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.DomainsOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.ipv4RoutingDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.ipv4Routing" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="ipv4Domains"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
<a-alert type="warning" :style="{ textAlign: 'center', marginTop: '20px' }">
|
||||||
|
<template slot="message">
|
||||||
|
<a-icon type="exclamation-circle" theme="filled" :style="{ color: '#FFA031' }"></a-icon>
|
||||||
|
{{ i18n "pages.xray.warpRoutingDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-row>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.warpRouting" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<template v-if="WarpExist">
|
||||||
|
<a-select mode="tags" :style="{ width: '100%' }" v-model="warpDomains"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">
|
||||||
|
<span>[[ p.label ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="6" header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||||
|
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||||
|
<a-button type="danger" @click="resetXrayConfigToDefault">
|
||||||
|
<span>{{ i18n "pages.settings.resetDefaultConfig" }}</span>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
149
web/html/settings/xray/dns.html
Normal file
149
web/html/settings/xray/dns.html
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{{define "settings/xray/dns"}}
|
||||||
|
<a-collapse default-active-key="1">
|
||||||
|
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.enable" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.enableDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="enableDNS"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<template v-if="enableDNS">
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.tag" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.tagDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="dnsTag"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.clientIp" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.clientIpDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-input type="text" v-model="dnsClientIp"></a-input>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.strategy" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.strategyDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select v-model="dnsStrategy" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||||
|
<span>[[ l ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.disableCache" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.disableCacheDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="dnsDisableCache"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.disableFallback" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.disableFallbackDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="dnsDisableFallback"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>{{ i18n "pages.xray.dns.disableFallbackIfMatch" }}</template>
|
||||||
|
<template #description>{{ i18n "pages.xray.dns.disableFallbackIfMatchDesc" }}</template>
|
||||||
|
<template #control>
|
||||||
|
<a-switch v-model="dnsDisableFallbackIfMatch"></a-switch>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
|
</template>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<template v-if="enableDNS">
|
||||||
|
<a-collapse-panel key="2" header='DNS'>
|
||||||
|
<template v-if="dnsServers.length > 0">
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-button type="primary" icon="plus" @click="addDNSServer()">
|
||||||
|
<span>{{ i18n "pages.xray.dns.add" }}</span>
|
||||||
|
</a-button>
|
||||||
|
<a-table :columns="dnsColumns" bordered :row-key="r => r.key" :data-source="dnsServers"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
||||||
|
<template slot="action" slot-scope="text,dns,index">
|
||||||
|
<span>[[ index+1 ]]</span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editDNSServer(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
<span>{{ i18n "edit" }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteDNSServer(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon>
|
||||||
|
<span>{{ i18n "delete"}}</span>
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="address" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
||||||
|
<span v-else>[[ dns ]]</span>
|
||||||
|
</template>
|
||||||
|
<template slot="domain" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||||
|
</template>
|
||||||
|
<template slot="expectIPs" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-empty description='{{ i18n "emptyDnsDesc" }}' :style="{ margin: '10px' }">
|
||||||
|
<a-button type="primary" icon="plus" @click="addDNSServer()" :style="{ marginTop: '10px' }">
|
||||||
|
<span>{{ i18n "pages.xray.dns.add" }}</span>
|
||||||
|
</a-button>
|
||||||
|
</a-empty>
|
||||||
|
</template>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel key="3" header='Fake DNS'>
|
||||||
|
<template v-if="fakeDns && fakeDns.length > 0">
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-button type="primary" icon="plus" @click="addFakedns()">{{ i18n "pages.xray.fakedns.add"
|
||||||
|
}}</a-button>
|
||||||
|
<a-table :columns="fakednsColumns" bordered :row-key="r => r.key" :data-source="fakeDns"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
||||||
|
<template slot="action" slot-scope="text,fakedns,index">
|
||||||
|
<span>[[ index+1 ]]</span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editFakedns(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
<span>{{ i18n "edit" }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteFakedns(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon>
|
||||||
|
<span>{{ i18n "delete"}}</span>
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-empty description='{{ i18n "emptyFakeDnsDesc" }}' :style="{ margin: '20px' }">
|
||||||
|
<a-button type="primary" icon="plus" @click="addFakedns()" :style="{ marginTop: '10px' }">
|
||||||
|
<span>{{ i18n "pages.xray.fakedns.add" }}</span>
|
||||||
|
</a-button>
|
||||||
|
</a-empty>
|
||||||
|
</template>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</template>
|
||||||
|
</a-collapse>
|
||||||
|
{{end}}
|
||||||
74
web/html/settings/xray/outbounds.html
Normal file
74
web/html/settings/xray/outbounds.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{{define "settings/xray/outbounds"}}
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-row>
|
||||||
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
|
<a-space direction="horizontal" size="small">
|
||||||
|
<a-button type="primary" icon="plus" @click="addOutbound()">
|
||||||
|
{{ i18n "pages.xray.outbound.addOutbound" }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="12" :sm="12" :lg="12" :style="{ textAlign: 'right' }">
|
||||||
|
<a-button-group>
|
||||||
|
<a-button icon="sync" @click="refreshOutboundTraffic()" :loading="refreshing"></a-button>
|
||||||
|
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o"
|
||||||
|
:style="{ color: themeSwitcher.isDarkTheme ? '#008771' : '#008771' }"></a-icon>
|
||||||
|
<a-button icon="retweet"></a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-button-group>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-table :columns="outboundColumns" bordered :row-key="r => r.key" :data-source="outboundData"
|
||||||
|
:scroll="isMobile ? {} : { x: 800 }" :pagination="false" :indent-size="0">
|
||||||
|
<template slot="action" slot-scope="text, outbound, index">
|
||||||
|
<span>[[ index+1 ]]</span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
|
||||||
|
<a-icon type="vertical-align-top"></a-icon>
|
||||||
|
<span>{{ i18n "pages.xray.rules.first"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="editOutbound(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
<span>{{ i18n "edit" }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetOutboundTraffic(index)">
|
||||||
|
<span>
|
||||||
|
<a-icon type="retweet"></a-icon>
|
||||||
|
<span>{{ i18n "pages.inbounds.resetTraffic"}}</span>
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteOutbound(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon>
|
||||||
|
<span>{{ i18n "delete"}}</span>
|
||||||
|
</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>
|
||||||
|
<template slot="traffic" slot-scope="text, outbound, index">
|
||||||
|
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-space>
|
||||||
|
{{end}}
|
||||||
38
web/html/settings/xray/reverse.html
Normal file
38
web/html/settings/xray/reverse.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{{define "settings/xray/reverse"}}
|
||||||
|
<template v-if="reverseData.length > 0">
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-button type="primary" icon="plus" @click="addReverse()">
|
||||||
|
<span>{{ i18n "pages.xray.outbound.addReverse" }}</span>
|
||||||
|
</a-button>
|
||||||
|
<a-table :columns="reverseColumns" bordered :row-key="r => r.key" :data-source="reverseData"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
||||||
|
<template slot="action" slot-scope="text, reverse, index">
|
||||||
|
<span>[[ index+1 ]]</span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editReverse(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
<span>{{ i18n "edit" }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteReverse(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon>
|
||||||
|
<span>{{ i18n "delete"}}</span>
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-empty description='{{ i18n "emptyReverseDesc" }}' :style="{ margin: '10px' }">
|
||||||
|
<a-button type="primary" icon="plus" @click="addReverse()" :style="{ marginTop: '10px' }">
|
||||||
|
{{ i18n "pages.xray.outbound.addReverse" }}
|
||||||
|
</a-button>
|
||||||
|
</a-empty>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
119
web/html/settings/xray/routing.html
Normal file
119
web/html/settings/xray/routing.html
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{{define "settings/xray/routing"}}
|
||||||
|
<a-space direction="vertical" size="middle">
|
||||||
|
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
||||||
|
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered :row-key="r => r.key"
|
||||||
|
:data-source="routingRuleData" :scroll="isMobile ? {} : { x: 1000 }" :pagination="false" :indent-size="0"
|
||||||
|
v-on:onSort="replaceRule">
|
||||||
|
<template slot="action" slot-scope="text, rule, index">
|
||||||
|
<a-table-sort-trigger :item-index="index"></a-table-sort-trigger>
|
||||||
|
<span class="ant-table-row-index"> [[ index+1 ]] </span>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
:style="{ fontSize: '16px', textDecoration: 'bold' }"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="index>0" @click="replaceRule(index,0)">
|
||||||
|
<a-icon type="vertical-align-top"></a-icon>
|
||||||
|
{{ i18n "pages.xray.rules.first"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="index>0" @click="replaceRule(index,index-1)">
|
||||||
|
<a-icon type="arrow-up"></a-icon>
|
||||||
|
{{ i18n "pages.xray.rules.up"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,index+1)">
|
||||||
|
<a-icon type="arrow-down"></a-icon>
|
||||||
|
{{ i18n "pages.xray.rules.down"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="index<routingRuleData.length-1"
|
||||||
|
@click="replaceRule(index,routingRuleData.length-1)">
|
||||||
|
<a-icon type="vertical-align-bottom"></a-icon>
|
||||||
|
{{ i18n "pages.xray.rules.last"}}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="editRule(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteRule(index)">
|
||||||
|
<span :style="{ color: '#FF4D4F' }">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="inbound" slot-scope="text, rule, index">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-if="rule.inboundTag">Inbound Tag: [[ rule.inboundTag ]]</p>
|
||||||
|
<p v-if="rule.user">User email: [[ rule.user ]]</p>
|
||||||
|
</template>
|
||||||
|
[[ [rule.inboundTag,rule.user].join('\n') ]]
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template slot="outbound" slot-scope="text, rule, index">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-if="rule.outboundTag">Outbound Tag: [[ rule.outboundTag ]]</p>
|
||||||
|
</template>
|
||||||
|
[[ rule.outboundTag ]]
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template slot="balancer" slot-scope="text, rule, index">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
|
||||||
|
</template>
|
||||||
|
[[ rule.balancerTag ]]
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, rule, index">
|
||||||
|
<a-popover placement="bottomRight"
|
||||||
|
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2" :style="{ maxWidth: '300px' }">
|
||||||
|
<tr v-if="rule.source">
|
||||||
|
<td>Source</td>
|
||||||
|
<td><a-tag color="blue" v-for="r in rule.source.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.sourcePort">
|
||||||
|
<td>Source Port</td>
|
||||||
|
<td><a-tag color="green" v-for="r in rule.sourcePort.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.network">
|
||||||
|
<td>Network</td>
|
||||||
|
<td><a-tag color="blue" v-for="r in rule.network.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.protocol">
|
||||||
|
<td>Protocol</td>
|
||||||
|
<td><a-tag color="green" v-for="r in rule.protocol.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.attrs">
|
||||||
|
<td>Attrs</td>
|
||||||
|
<td><a-tag color="blue" v-for="r in rule.attrs.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.ip">
|
||||||
|
<td>IP</td>
|
||||||
|
<td><a-tag color="green" v-for="r in rule.ip.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.domain">
|
||||||
|
<td>Domain</td>
|
||||||
|
<td><a-tag color="blue" v-for="r in rule.domain.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.port">
|
||||||
|
<td>Port</td>
|
||||||
|
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="rule.balancerTag">
|
||||||
|
<td>Balancer Tag</td>
|
||||||
|
<td><a-tag color="blue">[[ rule.balancerTag ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-button shape="round" size="small" :style="{ fontSize: '14px', padding: '0 10px' }">
|
||||||
|
<a-icon type="info"></a-icon>
|
||||||
|
</a-button>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</a-table-sortable>
|
||||||
|
</a-space>
|
||||||
|
{{end}}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/xq.min.css?{{ .cur_ver }}">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/codemirror/lint/lint.css">
|
||||||
|
|
||||||
<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/js/model/outbound.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/codemirror/codemirror.min.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
<script src="{{ .base_path }}assets/codemirror/javascript.js"></script>
|
||||||
@@ -40,32 +39,21 @@
|
|||||||
.ant-list-item {
|
.ant-list-item {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.collapse-title {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-bottom: 2px solid;
|
|
||||||
}
|
|
||||||
.collapse-title>i {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.ant-collapse-content-box>li {
|
|
||||||
padding: 12px 0 0 !important;
|
|
||||||
}
|
|
||||||
.ant-list-item>li {
|
.ant-list-item>li {
|
||||||
padding: 10px 20px !important;
|
padding: 10px 20px !important;
|
||||||
}
|
}
|
||||||
|
.ant-collapse-content-box .ant-alert {
|
||||||
|
margin-block-end: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
<a-sidebar></a-sidebar>
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" :style="{ marginBottom: '10px' }"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
color="red"
|
color="red"
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
@@ -73,17 +61,17 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable :style="{ marginBottom: '.5rem' }">
|
||||||
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
<a-row :style="{ display: 'flex', flexWrap: 'wrap', alignItems: 'center' }">
|
||||||
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" :style="{ padding: '4px' }">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateXraySetting">{{ i18n "pages.xray.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartXray">{{ i18n "pages.xray.restart" }}</a-button>
|
||||||
<a-popover v-if="restartResult"
|
<a-popover v-if="restartResult"
|
||||||
:overlay-class-name="themeSwitcher.currentTheme">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<span slot="title" style="font-size: 12pt">Error in running xray-core</span>
|
<span slot="title">{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p style="max-width: 400px" v-for="line in restartResult.split('\n')">[[ line ]]</p>
|
<span :style="{ maxWidth: '400px' }" v-for="line in restartResult.split('\n')">[[ line ]]</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
@@ -93,7 +81,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
|
<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>
|
<a-alert type="warning" :style="{ float: 'right', width: 'fit-content' }" message='{{ i18n "pages.settings.infoDesc" }}' show-icon>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -103,683 +91,26 @@
|
|||||||
<a-tabs class="ant-card-dark-box-nohover" default-active-key="1"
|
<a-tabs class="ant-card-dark-box-nohover" default-active-key="1"
|
||||||
@change="(activeKey) => { this.changePage(activeKey); }"
|
@change="(activeKey) => { this.changePage(activeKey); }"
|
||||||
:class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-tab-pane key="tpl-basic" tab='{{ i18n "pages.xray.basicTemplate"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-basic" tab='{{ i18n "pages.xray.basicTemplate"}}' :style="{ paddingTop: '20px' }">
|
||||||
<a-collapse>
|
{{ template "settings/xray/basics" . }}
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.generalConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.FreedomStrategy" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.FreedomStrategyDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select 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>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.RoutingStrategy" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.RoutingStrategyDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.statistics" }}'>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.statsInboundUplink" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.statsInboundUplinkDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="statsInboundUplink"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.statsInboundDownlink" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.statsInboundDownlinkDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="statsInboundDownlink"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.statsOutboundUplink" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.statsOutboundUplinkDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="statsOutboundUplink"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.statsOutboundDownlink" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.statsOutboundDownlinkDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="statsOutboundDownlink"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.logConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.logLevel" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.logLevelDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option v-for="s in log.loglevel" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.accessLog" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.accessLogDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option value=''>Empty</a-select-option>
|
|
||||||
<a-select-option v-for="s in log.access" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.errorLog" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.errorLogDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option value=''>Empty</a-select-option>
|
|
||||||
<a-select-option v-for="s in log.error" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.maskAddress" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.maskAddressDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
|
||||||
<a-select-option value=''>Empty</a-select-option>
|
|
||||||
<a-select-option v-for="s in log.maskAddress" :value="s">[[ s ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dnsLog"}}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dnsLogDesc"}}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="dnslog"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.blockConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.Torrent"}}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.TorrentDesc"}}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="torrentSettings"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.Family"}}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.FamilyDesc"}}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="familyProtectSettings"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.basicRouting"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.blockips" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select mode="tags" v-model="blockedIPs" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.blockdomains" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select mode="tags" v-model="blockedDomains" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.BlockDomainsOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center; margin-top: 20px;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.directConnectionsConfigsDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select mode="tags" style="width: 100%" v-model="directIPs" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.IPsOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select mode="tags" style="width: 100%" v-model="directDomains" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.DomainsOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center; margin-top: 20px;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.ipv4RoutingDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.ipv4Routing" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select mode="tags" style="width: 100%" v-model="ipv4Domains" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<a-alert type="warning" style="text-align: center; margin-top: 20px;">
|
|
||||||
<template slot="message">
|
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
|
||||||
{{ i18n "pages.xray.warpRoutingDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-alert>
|
|
||||||
</a-row>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.warpRouting" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<template v-if="WarpExist">
|
|
||||||
<a-select mode="tags" style="width: 100%" v-model="warpDomains" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="p.value" :label="p.label" v-for="p in settingsData.ServicesOptions">[[ p.label ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="danger" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-routing" tab='{{ i18n "pages.xray.Routings"}}' style="padding-top: 20px;">
|
<a-tab-pane key="tpl-routing" tab='{{ i18n "pages.xray.Routings"}}' :style="{ paddingTop: '20px' }">
|
||||||
<a-space direction="vertical" size="middle">
|
{{ template "settings/xray/routing" . }}
|
||||||
<a-button type="primary" icon="plus" @click="addRule">{{ i18n "pages.xray.rules.add" }}</a-button>
|
|
||||||
<a-table-sortable :columns="isMobile ? rulesMobileColumns : rulesColumns" bordered
|
|
||||||
:row-key="r => r.key"
|
|
||||||
:data-source="routingRuleData"
|
|
||||||
:scroll="isMobile ? {} : { x: 1000 }"
|
|
||||||
:pagination="false"
|
|
||||||
:indent-size="0"
|
|
||||||
v-on:onSort="replaceRule">
|
|
||||||
<template slot="action" slot-scope="text, rule, index">
|
|
||||||
<a-table-sort-trigger :item-index="index"></a-table-sort-trigger>
|
|
||||||
<span class="ant-table-row-index"> [[ index+1 ]] </span>
|
|
||||||
<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 v-if="index>0" @click="replaceRule(index,0)">
|
|
||||||
<a-icon type="vertical-align-top"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.first"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item v-if="index>0" @click="replaceRule(index,index-1)">
|
|
||||||
<a-icon type="arrow-up"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.up"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,index+1)">
|
|
||||||
<a-icon type="arrow-down"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.down"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item v-if="index<routingRuleData.length-1" @click="replaceRule(index,routingRuleData.length-1)">
|
|
||||||
<a-icon type="vertical-align-bottom"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.last"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="editRule(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteRule(index)">
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<template slot="inbound" slot-scope="text, rule, index">
|
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-if="rule.inboundTag">Inbound Tag: [[ rule.inboundTag ]]</p>
|
|
||||||
<p v-if="rule.user">User email: [[ rule.user ]]</p>
|
|
||||||
</template>
|
|
||||||
[[ [rule.inboundTag,rule.user].join('\n') ]]
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
<template slot="outbound" slot-scope="text, rule, index">
|
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-if="rule.outboundTag">Outbound Tag: [[ rule.outboundTag ]]</p>
|
|
||||||
</template>
|
|
||||||
[[ rule.outboundTag ]]
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
<template slot="balancer" slot-scope="text, rule, index">
|
|
||||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<template slot="content">
|
|
||||||
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
|
|
||||||
</template>
|
|
||||||
[[ rule.balancerTag ]]
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
<template slot="info" slot-scope="text, rule, index">
|
|
||||||
<a-popover placement="bottomRight"
|
|
||||||
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
|
||||||
:overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
|
||||||
<template slot="content">
|
|
||||||
<table cellpadding="2" style="max-width: 300px;">
|
|
||||||
<tr v-if="rule.source">
|
|
||||||
<td>Source</td>
|
|
||||||
<td><a-tag color="blue" v-for="r in rule.source.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.sourcePort">
|
|
||||||
<td>Source Port</td>
|
|
||||||
<td><a-tag color="green" v-for="r in rule.sourcePort.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.network">
|
|
||||||
<td>Network</td>
|
|
||||||
<td><a-tag color="blue" v-for="r in rule.network.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.protocol">
|
|
||||||
<td>Protocol</td>
|
|
||||||
<td><a-tag color="green" v-for="r in rule.protocol.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.attrs">
|
|
||||||
<td>Attrs</td>
|
|
||||||
<td><a-tag color="blue" v-for="r in rule.attrs.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.ip">
|
|
||||||
<td>IP</td>
|
|
||||||
<td><a-tag color="green" v-for="r in rule.ip.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.domain">
|
|
||||||
<td>Domain</td>
|
|
||||||
<td><a-tag color="blue" v-for="r in rule.domain.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.port">
|
|
||||||
<td>Port</td>
|
|
||||||
<td><a-tag color="green" v-for="r in rule.port.split(',')">[[ r ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="rule.balancerTag">
|
|
||||||
<td>Balancer Tag</td>
|
|
||||||
<td><a-tag color="blue">[[ rule.balancerTag ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</template>
|
|
||||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
|
||||||
<a-icon type="info"></a-icon>
|
|
||||||
</a-button>
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
</a-table-sortable>
|
|
||||||
</a-space>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-outbound" tab='{{ i18n "pages.xray.Outbounds"}}' force-render="true">
|
<a-tab-pane key="tpl-outbound" tab='{{ i18n "pages.xray.Outbounds"}}' force-render="true">
|
||||||
<a-space direction="vertical" size="middle">
|
{{ template "settings/xray/outbounds" . }}
|
||||||
<a-row>
|
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
|
||||||
<a-space direction="horizontal" size="small">
|
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()">
|
|
||||||
{{ i18n "pages.xray.outbound.addOutbound" }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" icon="cloud" @click="showWarp()">WARP</a-button>
|
|
||||||
</a-space>
|
|
||||||
</a-col>
|
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
|
||||||
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
|
|
||||||
<a-popconfirm placement="topRight" @confirm="resetOutboundTraffic(-1)"
|
|
||||||
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
|
||||||
: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: #008771' : 'color: #008771'"></a-icon>
|
|
||||||
<a-icon type="retweet" style="cursor: pointer;"></a-icon>
|
|
||||||
</a-popconfirm>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
<a-table :columns="outboundColumns" bordered
|
|
||||||
:row-key="r => r.key"
|
|
||||||
:data-source="outboundData"
|
|
||||||
:scroll="isMobile ? {} : { x: 800 }"
|
|
||||||
:pagination="false"
|
|
||||||
:indent-size="0">
|
|
||||||
<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 v-if="index>0" @click="setFirstOutbound(index)">
|
|
||||||
<a-icon type="vertical-align-top"></a-icon>
|
|
||||||
{{ i18n "pages.xray.rules.first"}}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="editOutbound(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="resetOutboundTraffic(index)">
|
|
||||||
<span>
|
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic"}}
|
|
||||||
</span>
|
|
||||||
</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>
|
|
||||||
<template slot="traffic" slot-scope="text, outbound, index">
|
|
||||||
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-space>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-reverse" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-reverse" tab='{{ i18n "pages.xray.outbound.reverse"}}' :style="{ paddingTop: '20px' }" force-render="true">
|
||||||
<template v-if="reverseData.length > 0">
|
{{ template "settings/xray/reverse" . }}
|
||||||
<a-space direction="vertical" size="middle">
|
|
||||||
<a-button type="primary" icon="plus" @click="addReverse()">
|
|
||||||
{{ 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">
|
|
||||||
<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-space>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-empty description='{{ i18n "emptyReverseDesc" }}' style="margin: 10px;">
|
|
||||||
<a-button type="primary" icon="plus" @click="addReverse()" style="margin-top: 10px;">
|
|
||||||
{{ i18n "pages.xray.outbound.addReverse" }}
|
|
||||||
</a-button>
|
|
||||||
</a-empty>
|
|
||||||
</template>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-balancer" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-balancer" tab='{{ i18n "pages.xray.Balancers"}}' :style="{ paddingTop: '20px' }" force-render="true">
|
||||||
<template v-if="balancersData.length > 0">
|
{{ template "settings/xray/balancers" . }}
|
||||||
<a-space direction="vertical" size="middle">
|
|
||||||
<a-button type="primary" icon="plus" @click="addBalancer()">
|
|
||||||
{{ i18n "pages.xray.balancer.addBalancer"}}
|
|
||||||
</a-button>
|
|
||||||
<a-table :columns="balancerColumns" bordered
|
|
||||||
:row-key="r => r.key"
|
|
||||||
:data-source="balancersData"
|
|
||||||
:scroll="isMobile ? {} : { x: 200 }"
|
|
||||||
:pagination="false"
|
|
||||||
:indent-size="0">
|
|
||||||
<template slot="action" slot-scope="text, balancer, 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="editBalancer(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteBalancer(index)">
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<template slot="strategy" slot-scope="text, balancer, index">
|
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='leastLoad'" color="green">Least Load</a-tag>
|
|
||||||
<a-tag style="margin:0;" v-if="balancer.strategy=='leastPing'" color="green">Least Ping</a-tag>
|
|
||||||
</template>
|
|
||||||
<template slot="selector" slot-scope="text, balancer, index">
|
|
||||||
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
<a-radio-group
|
|
||||||
v-if="observatoryEnable || burstObservatoryEnable"
|
|
||||||
v-model="obsSettings"
|
|
||||||
@change="changeObsCode"
|
|
||||||
button-style="solid"
|
|
||||||
:size="isMobile ? 'small' : ''">
|
|
||||||
<a-radio-button value="observatory" v-if="observatoryEnable">Observatory</a-radio-button>
|
|
||||||
<a-radio-button value="burstObservatory" v-if="burstObservatoryEnable">Burst Observatory</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<textarea style="position:absolute; left: -800px;" id="obsSetting"></textarea>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-empty description='{{ i18n "emptyBalancersDesc" }}' style="margin: 10px;">
|
|
||||||
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-top: 10px;">
|
|
||||||
{{ i18n "pages.xray.balancer.addBalancer"}}
|
|
||||||
</a-button>
|
|
||||||
</a-empty>
|
|
||||||
</template>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-dns" tab='DNS' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-dns" tab='DNS' :style="{ paddingTop: '20px' }" force-render="true">
|
||||||
<a-collapse>
|
{{ template "settings/xray/dns" . }}
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
</a-tab-pane>
|
||||||
<a-setting-list-item paddings="small">
|
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' :style="{ paddingTop: '20px' }" force-render="true">
|
||||||
<template #title>{{ i18n "pages.xray.dns.enable" }}</template>
|
{{ template "settings/xray/advanced" . }}
|
||||||
<template #description>{{ i18n "pages.xray.dns.enableDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="enableDNS"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<template v-if="enableDNS">
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.tag" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.tagDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-input type="text" v-model="dnsTag"></a-input>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.clientIp" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.clientIpDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-input type="text" v-model="dnsClientIp"></a-input>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.strategy" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.strategyDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-select v-model="dnsStrategy" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
|
||||||
[[ l ]]
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.disableCache" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.disableCacheDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="dnsDisableCache"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.disableFallback" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.disableFallbackDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="dnsDisableFallback"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
<a-setting-list-item paddings="small">
|
|
||||||
<template #title>{{ i18n "pages.xray.dns.disableFallbackIfMatch" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.xray.dns.disableFallbackIfMatchDesc" }}</template>
|
|
||||||
<template #control>
|
|
||||||
<a-switch v-model="dnsDisableFallbackIfMatch"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-setting-list-item>
|
|
||||||
</template>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<template v-if="enableDNS">
|
|
||||||
<a-collapse-panel header='DNS'>
|
|
||||||
<template v-if="dnsServers.length > 0">
|
|
||||||
<a-space direction="vertical" size="middle">
|
|
||||||
<a-button type="primary" icon="plus" @click="addDNSServer()">{{ i18n
|
|
||||||
"pages.xray.dns.add" }}</a-button>
|
|
||||||
<a-table :columns="dnsColumns" bordered :row-key="r => r.key"
|
|
||||||
:data-source="dnsServers" :scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
|
||||||
<template slot="action" slot-scope="text,dns,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="editDNSServer(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteDNSServer(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="dns,index">
|
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
|
||||||
<span v-else>[[ dns ]]</span>
|
|
||||||
</template>
|
|
||||||
<template slot="domain" slot-scope="dns,index">
|
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
|
||||||
</template>
|
|
||||||
<template slot="expectIPs" slot-scope="dns,index">
|
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-empty description='{{ i18n "emptyDnsDesc" }}' style="margin: 10px;">
|
|
||||||
<a-button type="primary" icon="plus" @click="addDNSServer()" style="margin-top: 10px;">{{ i18n "pages.xray.dns.add" }}</a-button>
|
|
||||||
</a-empty>
|
|
||||||
</template>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='Fake DNS'>
|
|
||||||
<template v-if="fakeDns && fakeDns.length > 0">
|
|
||||||
<a-space direction="vertical" size="middle">
|
|
||||||
<a-button type="primary" icon="plus" @click="addFakedns()">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
|
||||||
<a-table :columns="fakednsColumns" bordered :row-key="r => r.key"
|
|
||||||
:data-source="fakeDns" :scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0">
|
|
||||||
<template slot="action" slot-scope="text,fakedns,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="editFakedns(index)">
|
|
||||||
<a-icon type="edit"></a-icon>
|
|
||||||
{{ i18n "edit" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item @click="deleteFakedns(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-space>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-empty description='{{ i18n "emptyFakeDnsDesc" }}' style="margin: 20px;">
|
|
||||||
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-top: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
|
||||||
</a-empty>
|
|
||||||
</template>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</template>
|
|
||||||
</a-collapse>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
|
||||||
<a-space direction="vertical" size="small">
|
|
||||||
<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>
|
|
||||||
<a-radio-button value="inboundSettings">{{ i18n "pages.xray.Inbounds" }}</a-radio-button>
|
|
||||||
<a-radio-button value="outboundSettings">{{ i18n "pages.xray.Outbounds" }}</a-radio-button>
|
|
||||||
<a-radio-button value="routingRuleSettings">{{ i18n "pages.xray.Routings" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<textarea style="position:absolute; left: -800px;" id="xraySetting"></textarea>
|
|
||||||
</a-space>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
@@ -788,16 +119,17 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/aSidebar" .}}
|
||||||
{{template "component/sortableTable" .}}
|
{{template "component/aThemeSwitch" .}}
|
||||||
{{template "component/setting"}}
|
{{template "component/aTableSortable" .}}
|
||||||
{{template "ruleModal"}}
|
{{template "component/aSettingListItem" .}}
|
||||||
{{template "outModal"}}
|
{{template "modals/ruleModal"}}
|
||||||
{{template "reverseModal"}}
|
{{template "modals/outModal"}}
|
||||||
{{template "balancerModal"}}
|
{{template "modals/reverseModal"}}
|
||||||
{{template "dnsModal"}}
|
{{template "modals/balancerModal"}}
|
||||||
{{template "fakednsModal"}}
|
{{template "modals/dnsModal"}}
|
||||||
{{template "warpModal"}}
|
{{template "modals/fakednsModal"}}
|
||||||
|
{{template "modals/warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
{ title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
|
{ title: "#", align: 'center', width: 15, scopedSlots: { customRender: 'action' } },
|
||||||
@@ -863,9 +195,9 @@
|
|||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
|
mixins: [MediaQueryMixin],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
|
||||||
themeSwitcher,
|
themeSwitcher,
|
||||||
isDarkTheme: themeSwitcher.isDarkTheme,
|
isDarkTheme: themeSwitcher.isDarkTheme,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
@@ -877,7 +209,6 @@
|
|||||||
refreshing: false,
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
isMobile: window.innerWidth <= 768,
|
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
obsSettings: '',
|
obsSettings: '',
|
||||||
cm: null,
|
cm: null,
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
{{define "menuItems"}}
|
|
||||||
<a-menu-item key="{{ .base_path }}panel/">
|
|
||||||
<a-icon type="dashboard"></a-icon>
|
|
||||||
<span>
|
|
||||||
<b>{{ i18n "menu.dashboard"}}</b>
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
|
||||||
<a-icon type="user"></a-icon>
|
|
||||||
<span>
|
|
||||||
<b>{{ i18n "menu.inbounds"}}</b>
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
|
||||||
<a-icon type="setting"></a-icon>
|
|
||||||
<span>
|
|
||||||
<b>{{ i18n "menu.settings"}}</b>
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="{{ .base_path }}panel/xray">
|
|
||||||
<a-icon type="tool"></a-icon>
|
|
||||||
<span>
|
|
||||||
<b>{{ i18n "menu.xray"}}</b>
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
|
||||||
<a-icon type="logout"></a-icon>
|
|
||||||
<span>
|
|
||||||
<b>{{ i18n "menu.logout"}}</b>
|
|
||||||
</span>
|
|
||||||
</a-menu-item>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
{{define "commonSider"}}
|
|
||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md">
|
|
||||||
<a-theme-switch></a-theme-switch>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
|
||||||
{{template "menuItems" .}}
|
|
||||||
</a-menu>
|
|
||||||
</a-layout-sider>
|
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false" @close="siderDrawer.close()" :visible="siderDrawer.visible" :wrap-class-name="themeSwitcher.currentTheme" :wrap-style="{ padding: 0 }">
|
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
|
||||||
</div>
|
|
||||||
<a-theme-switch></a-theme-switch>
|
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
|
||||||
{{template "menuItems" .}}
|
|
||||||
</a-menu>
|
|
||||||
</a-drawer>
|
|
||||||
<script>
|
|
||||||
const siderDrawer = {
|
|
||||||
visible: false,
|
|
||||||
show() {
|
|
||||||
this.visible = true;
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.visible = false;
|
|
||||||
},
|
|
||||||
change() {
|
|
||||||
this.visible = !this.visible;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
{{define "component/passwordInput"}}
|
|
||||||
<template>
|
|
||||||
<a-input :value="value" :type="showPassword ? 'text' : 'password'" :placeholder="placeholder"
|
|
||||||
:autocomplete="autocomplete" :name="name" @input="$emit('input', $event.target.value)">
|
|
||||||
<template v-if="icon" #prefix>
|
|
||||||
<a-icon :type="icon" style="font-size: 16px;" />
|
|
||||||
</template>
|
|
||||||
<template #addonAfter>
|
|
||||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'" @click="toggleShowPassword" style="font-size: 16px;" />
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</template>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "component/password"}}
|
|
||||||
<script>
|
|
||||||
Vue.component('a-password-input', {
|
|
||||||
props: {
|
|
||||||
'title': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'value': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'placeholder': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'autocomplete': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'name': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'icon': {
|
|
||||||
type: undefined,
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `{{template "component/passwordInput"}}`,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showPassword: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleShowPassword() {
|
|
||||||
this.showPassword = !this.showPassword;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,672 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
{{template "head" .}}
|
|
||||||
<style>
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
.ant-layout-content {
|
|
||||||
margin: 24px 16px;
|
|
||||||
}
|
|
||||||
.ant-card-hoverable {
|
|
||||||
margin-inline: 0.3rem;
|
|
||||||
}
|
|
||||||
.ant-alert-error {
|
|
||||||
margin-inline: 0.3rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-col-sm-24 {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.ant-card-dark h2 {
|
|
||||||
color: var(--dark-color-text-primary);
|
|
||||||
}
|
|
||||||
.ant-backup-list-item {
|
|
||||||
gap: 10px;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.dark .ant-backup-list-item svg {
|
|
||||||
color: var(--dark-color-text-primary);
|
|
||||||
}
|
|
||||||
.dark .ant-backup-list,
|
|
||||||
.dark .ant-xray-version-list {
|
|
||||||
border-color: var(--dark-color-stroke);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
|
||||||
{{ template "commonSider" . }}
|
|
||||||
<a-layout id="content-layout">
|
|
||||||
<a-layout-content>
|
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip">
|
|
||||||
<transition name="list" appear>
|
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
|
||||||
color="red"
|
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
|
||||||
show-icon closable>
|
|
||||||
</a-alert>
|
|
||||||
</transition>
|
|
||||||
<transition name="list" appear>
|
|
||||||
<a-row>
|
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.cpu.color"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
|
||||||
<div><b>CPU:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
|
|
||||||
<a-icon type="area-chart"></a-icon>
|
|
||||||
<template slot="title">
|
|
||||||
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div>
|
|
||||||
<div><b>Speed:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
|
||||||
</template>
|
|
||||||
</a-tooltip></div>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.mem.color"
|
|
||||||
:percent="status.mem.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.swap.color"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>Swap:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.disk.color"
|
|
||||||
:percent="status.disk.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-row>
|
|
||||||
</transition>
|
|
||||||
<transition name="list" appear>
|
|
||||||
<a-row>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>3X-UI:</b>
|
|
||||||
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
|
||||||
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
|
||||||
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
|
|
||||||
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
|
||||||
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]] </a-tag>
|
|
||||||
<a-popover v-if="status.xray.state === State.Error" :overlay-class-name="themeSwitcher.currentTheme">
|
|
||||||
<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"></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>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "menu.link" }}:</b>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
|
||||||
<a-tag color="green">
|
|
||||||
<a-tooltip>
|
|
||||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "usage"}}:</b>
|
|
||||||
<a-tag color="green"> RAM: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
|
|
||||||
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="global"></a-icon> IPv4
|
|
||||||
<template slot="title">
|
|
||||||
[[ status.publicIP.ipv4 ]]
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="global"></a-icon> IPv6
|
|
||||||
<template slot="title">
|
|
||||||
[[ status.publicIP.ipv6 ]]
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="arrow-up"></a-icon> Up: [[ SizeFormatter.sizeFormat(status.netIO.up) ]]/s
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="arrow-down"></a-icon> Down: [[ SizeFormatter.sizeFormat(status.netIO.down) ]]/s
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.totalSent" }}
|
|
||||||
</template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12">
|
|
||||||
<a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<a-icon type="cloud-download"></a-icon>
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
|
||||||
</template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
|
|
||||||
</a-tooltip>
|
|
||||||
</a-tag>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</transition>
|
|
||||||
</a-spin>
|
|
||||||
</a-layout-content>
|
|
||||||
</a-layout>
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
|
||||||
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
|
||||||
<a-alert type="warning" style="margin-bottom: 12px; width: 100%;"
|
|
||||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
|
||||||
<a-list class="ant-xray-version-list" bordered style="width: 100%;">
|
|
||||||
<a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
|
|
||||||
<a-list-item-meta>
|
|
||||||
<template #title>[[ version ]]</template>
|
|
||||||
</a-list-item-meta>
|
|
||||||
<a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
|
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-modal>
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible"
|
|
||||||
:closable="true" @cancel="() => logModal.visible = false"
|
|
||||||
:class="themeSwitcher.currentTheme"
|
|
||||||
width="800px" footer="">
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.logs" }}
|
|
||||||
<a-icon :spin="logModal.loading"
|
|
||||||
type="sync"
|
|
||||||
style="vertical-align: middle; margin-left: 10px;"
|
|
||||||
:disabled="logModal.loading"
|
|
||||||
@click="openLogs()">
|
|
||||||
</a-icon>
|
|
||||||
</template>
|
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item style="margin-right: 0.5rem;">
|
|
||||||
<a-input-group compact>
|
|
||||||
<a-select size="small" v-model="logModal.rows" style="width:70px;"
|
|
||||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="10">10</a-select-option>
|
|
||||||
<a-select-option value="20">20</a-select-option>
|
|
||||||
<a-select-option value="50">50</a-select-option>
|
|
||||||
<a-select-option value="100">100</a-select-option>
|
|
||||||
<a-select-option value="500">500</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
<a-select size="small" v-model="logModal.level" style="width:95px;"
|
|
||||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
|
||||||
<a-select-option value="info">Info</a-select-option>
|
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
|
||||||
<a-select-option value="err">Error</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-input-group>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item style="float: right;">
|
|
||||||
<a-button type="primary" icon="download"
|
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs?.join('\n'))" download="x-ui.log">
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div>
|
|
||||||
</a-modal>
|
|
||||||
<a-modal id="backup-modal"
|
|
||||||
v-model="backupModal.visible"
|
|
||||||
title='{{ i18n "pages.index.backupTitle" }}'
|
|
||||||
:closable="true"
|
|
||||||
footer=""
|
|
||||||
:class="themeSwitcher.currentTheme">
|
|
||||||
<a-list class="ant-backup-list" bordered style="width: 100%;">
|
|
||||||
<a-list-item class="ant-backup-list-item" @click="exportDatabase()">
|
|
||||||
<a-list-item-meta>
|
|
||||||
<template #title>{{ i18n "pages.index.exportDatabase" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
|
|
||||||
</a-list-item-meta>
|
|
||||||
<a-icon type="right" />
|
|
||||||
</a-list-item>
|
|
||||||
<a-list-item class="ant-backup-list-item" @click="importDatabase()">
|
|
||||||
<a-list-item-meta>
|
|
||||||
<template #title>{{ i18n "pages.index.importDatabase" }}</template>
|
|
||||||
<template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
|
|
||||||
<templaet #avatar>
|
|
||||||
<a-icon type="import" />
|
|
||||||
</templaet>
|
|
||||||
</a-list-item-meta>
|
|
||||||
<a-icon type="right" />
|
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-modal>
|
|
||||||
</a-layout>
|
|
||||||
{{template "js" .}}
|
|
||||||
{{template "component/themeSwitcher" .}}
|
|
||||||
{{template "textModal"}}
|
|
||||||
<script>
|
|
||||||
const State = {
|
|
||||||
Running: "running",
|
|
||||||
Stop: "stop",
|
|
||||||
Error: "error",
|
|
||||||
}
|
|
||||||
Object.freeze(State);
|
|
||||||
|
|
||||||
class CurTotal {
|
|
||||||
|
|
||||||
constructor(current, total) {
|
|
||||||
this.current = current;
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
get percent() {
|
|
||||||
if (this.total === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return NumberFormatter.toFixed(this.current / this.total * 100, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get color() {
|
|
||||||
const percent = this.percent;
|
|
||||||
if (percent < 80) {
|
|
||||||
return '#008771'; // Green
|
|
||||||
} else if (percent < 90) {
|
|
||||||
return "#f37b24"; // Orange
|
|
||||||
} else {
|
|
||||||
return "#cf3c3c"; // Red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Status {
|
|
||||||
constructor(data) {
|
|
||||||
this.cpu = new CurTotal(0, 0);
|
|
||||||
this.cpuCores = 0;
|
|
||||||
this.logicalPro = 0;
|
|
||||||
this.cpuSpeedMhz = 0;
|
|
||||||
this.disk = new CurTotal(0, 0);
|
|
||||||
this.loads = [0, 0, 0];
|
|
||||||
this.mem = new CurTotal(0, 0);
|
|
||||||
this.netIO = { up: 0, down: 0 };
|
|
||||||
this.netTraffic = { sent: 0, recv: 0 };
|
|
||||||
this.publicIP = { ipv4: 0, ipv6: 0 };
|
|
||||||
this.swap = new CurTotal(0, 0);
|
|
||||||
this.tcpCount = 0;
|
|
||||||
this.udpCount = 0;
|
|
||||||
this.uptime = 0;
|
|
||||||
this.appUptime = 0;
|
|
||||||
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
|
||||||
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
|
||||||
|
|
||||||
if (data == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.cpu = new CurTotal(data.cpu, 100);
|
|
||||||
this.cpuCores = data.cpuCores;
|
|
||||||
this.logicalPro = data.logicalPro;
|
|
||||||
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
|
||||||
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
|
||||||
this.loads = data.loads.map(load => NumberFormatter.toFixed(load, 2));
|
|
||||||
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
|
||||||
this.netIO = data.netIO;
|
|
||||||
this.netTraffic = data.netTraffic;
|
|
||||||
this.publicIP = data.publicIP;
|
|
||||||
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
|
||||||
this.tcpCount = data.tcpCount;
|
|
||||||
this.udpCount = data.udpCount;
|
|
||||||
this.uptime = data.uptime;
|
|
||||||
this.appUptime = data.appUptime;
|
|
||||||
this.appStats = data.appStats;
|
|
||||||
this.xray = data.xray;
|
|
||||||
switch (this.xray.state) {
|
|
||||||
case State.Running:
|
|
||||||
this.xray.color = "green";
|
|
||||||
break;
|
|
||||||
case State.Stop:
|
|
||||||
this.xray.color = "orange";
|
|
||||||
break;
|
|
||||||
case State.Error:
|
|
||||||
this.xray.color = "red";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.xray.color = "gray";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionModal = {
|
|
||||||
visible: false,
|
|
||||||
versions: [],
|
|
||||||
show(versions) {
|
|
||||||
this.visible = true;
|
|
||||||
this.versions = versions;
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.visible = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const logModal = {
|
|
||||||
visible: false,
|
|
||||||
logs: [],
|
|
||||||
rows: 20,
|
|
||||||
level: 'info',
|
|
||||||
syslog: false,
|
|
||||||
loading: false,
|
|
||||||
show(logs) {
|
|
||||||
this.visible = true;
|
|
||||||
this.logs = logs;
|
|
||||||
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
|
||||||
},
|
|
||||||
formatLogs(logs) {
|
|
||||||
let formattedLogs = '';
|
|
||||||
const levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
|
|
||||||
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
|
|
||||||
|
|
||||||
logs.forEach((log, index) => {
|
|
||||||
let [data, message] = log.split(" - ",2);
|
|
||||||
const parts = data.split(" ")
|
|
||||||
if(index>0) formattedLogs += '<br>';
|
|
||||||
|
|
||||||
if (parts.length === 3) {
|
|
||||||
const d = parts[0];
|
|
||||||
const t = parts[1];
|
|
||||||
const level = parts[2];
|
|
||||||
const levelIndex = levels.indexOf(level,levels) || 5;
|
|
||||||
|
|
||||||
//formattedLogs += `<span style="color: gray;">${index + 1}.</span>`;
|
|
||||||
formattedLogs += `<span style="color: ${levelColors[0]};">${d} ${t}</span> `;
|
|
||||||
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${level}</span>`;
|
|
||||||
} else {
|
|
||||||
const levelIndex = levels.indexOf(data,levels) || 5;
|
|
||||||
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${data}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(message){
|
|
||||||
if(message.startsWith("XRAY:"))
|
|
||||||
message = "<b>XRAY: </b>" + message.substring(5);
|
|
||||||
else
|
|
||||||
message = "<b>X-UI: </b>" + message;
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedLogs += message ? ' - ' + message : '';
|
|
||||||
});
|
|
||||||
|
|
||||||
return formattedLogs;
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.visible = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const backupModal = {
|
|
||||||
visible: false,
|
|
||||||
show() {
|
|
||||||
this.visible = true;
|
|
||||||
},
|
|
||||||
hide() {
|
|
||||||
this.visible = false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const app = new Vue({
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
el: '#app',
|
|
||||||
data: {
|
|
||||||
siderDrawer,
|
|
||||||
themeSwitcher,
|
|
||||||
status: new Status(),
|
|
||||||
versionModal,
|
|
||||||
logModal,
|
|
||||||
backupModal,
|
|
||||||
spinning: false,
|
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
|
||||||
showAlert: false,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
|
||||||
this.spinning = spinning;
|
|
||||||
this.loadingTip = tip;
|
|
||||||
},
|
|
||||||
async getStatus() {
|
|
||||||
try {
|
|
||||||
const msg = await HttpUtil.post('/server/status');
|
|
||||||
if (msg.success) {
|
|
||||||
this.setStatus(msg.obj);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to get status:", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setStatus(data) {
|
|
||||||
this.status = new Status(data);
|
|
||||||
},
|
|
||||||
async openSelectV2rayVersion() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post('server/getXrayVersion');
|
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
versionModal.show(msg.obj);
|
|
||||||
},
|
|
||||||
switchV2rayVersion(version) {
|
|
||||||
this.$confirm({
|
|
||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
|
||||||
okText: '{{ i18n "confirm"}}',
|
|
||||||
class: themeSwitcher.currentTheme,
|
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
|
||||||
onOk: async () => {
|
|
||||||
versionModal.hide();
|
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
|
||||||
await HttpUtil.post(`/server/installXray/${version}`);
|
|
||||||
this.loading(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async stopXrayService() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post('server/stopXrayService');
|
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async restartXrayService() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post('server/restartXrayService');
|
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async openLogs(){
|
|
||||||
logModal.loading = true;
|
|
||||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logModal.show(msg.obj);
|
|
||||||
await PromiseUtil.sleep(500);
|
|
||||||
logModal.loading = false;
|
|
||||||
},
|
|
||||||
async openConfig() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
|
||||||
},
|
|
||||||
openBackup() {
|
|
||||||
backupModal.show();
|
|
||||||
},
|
|
||||||
exportDatabase() {
|
|
||||||
window.location = basePath + 'server/getDb';
|
|
||||||
},
|
|
||||||
importDatabase() {
|
|
||||||
const fileInput = document.createElement('input');
|
|
||||||
fileInput.type = 'file';
|
|
||||||
fileInput.accept = '.db';
|
|
||||||
fileInput.addEventListener('change', async (event) => {
|
|
||||||
const dbFile = event.target.files[0];
|
|
||||||
if (dbFile) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('db', dbFile);
|
|
||||||
backupModal.hide();
|
|
||||||
this.loading(true);
|
|
||||||
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.loading(false);
|
|
||||||
if (!uploadMsg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading(true);
|
|
||||||
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
|
|
||||||
this.loading(false);
|
|
||||||
if (restartMsg.success) {
|
|
||||||
this.loading(true);
|
|
||||||
await PromiseUtil.sleep(5000);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fileInput.click();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
if (window.location.protocol !== "https:") {
|
|
||||||
this.showAlert = true;
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
await this.getStatus();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
await PromiseUtil.sleep(2000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"slices"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -193,13 +194,7 @@ func (j *CheckClientIpJob) checkError(e error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||||
for _, v := range s {
|
return slices.Contains(s, str)
|
||||||
if v == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traf
|
|||||||
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestBody, err := json.Marshal(map[string]interface{}{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
requestBody, err := json.Marshal(map[string]any{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("parse client/inbound traffic failed:", err)
|
logger.Warning("parse client/inbound traffic failed:", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -48,13 +48,13 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
func createTemplateData(params []string, seperator ...string) map[string]any {
|
||||||
var sep string = "=="
|
var sep string = "=="
|
||||||
if len(seperator) > 0 {
|
if len(seperator) > 0 {
|
||||||
sep = seperator[0]
|
sep = seperator[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := make(map[string]interface{})
|
templateData := make(map[string]any)
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
parts := strings.SplitN(param, sep, 2)
|
parts := strings.SplitN(param, sep, 2)
|
||||||
templateData[parts[0]] = parts[1]
|
templateData[parts[0]] = parts[1]
|
||||||
|
|||||||
@@ -413,13 +413,13 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]any)
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -450,13 +450,13 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]any
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients := oldSettings["clients"].([]interface{})
|
oldClients := oldSettings["clients"].([]any)
|
||||||
oldClients = append(oldClients, interfaceClients...)
|
oldClients = append(oldClients, interfaceClients...)
|
||||||
|
|
||||||
oldSettings["clients"] = oldClients
|
oldSettings["clients"] = oldClients
|
||||||
@@ -489,7 +489,7 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
|||||||
if oldInbound.Protocol == "shadowsocks" {
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
cipher = oldSettings["method"].(string)
|
cipher = oldSettings["method"].(string)
|
||||||
}
|
}
|
||||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"id": client.ID,
|
"id": client.ID,
|
||||||
"security": client.Security,
|
"security": client.Security,
|
||||||
@@ -519,7 +519,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
|||||||
logger.Error("Load Old Data Error")
|
logger.Error("Load Old Data Error")
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -534,11 +534,11 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
|||||||
client_key = "email"
|
client_key = "email"
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
needApiDel := false
|
needApiDel := false
|
||||||
for _, client := range interfaceClients {
|
for _, client := range interfaceClients {
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]any)
|
||||||
c_id := c[client_key].(string)
|
c_id := c[client_key].(string)
|
||||||
if c_id == clientId {
|
if c_id == clientId {
|
||||||
email, _ = c["email"].(string)
|
email, _ = c["email"].(string)
|
||||||
@@ -607,13 +607,13 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]any)
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(data.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -662,12 +662,12 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]any
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
settingsClients := oldSettings["clients"].([]interface{})
|
settingsClients := oldSettings["clients"].([]any)
|
||||||
settingsClients[clientIndex] = interfaceClients[0]
|
settingsClients[clientIndex] = interfaceClients[0]
|
||||||
oldSettings["clients"] = settingsClients
|
oldSettings["clients"] = settingsClients
|
||||||
|
|
||||||
@@ -732,7 +732,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
if oldInbound.Protocol == "shadowsocks" {
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
cipher = oldSettings["method"].(string)
|
cipher = oldSettings["method"].(string)
|
||||||
}
|
}
|
||||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{
|
||||||
"email": clients[0].Email,
|
"email": clients[0].Email,
|
||||||
"id": clients[0].ID,
|
"id": clients[0].ID,
|
||||||
"security": clients[0].Security,
|
"security": clients[0].Security,
|
||||||
@@ -809,7 +809,7 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
|||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
if traffic.IsInbound {
|
if traffic.IsInbound {
|
||||||
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]any{
|
||||||
"up": gorm.Expr("up + ?", traffic.Up),
|
"up": gorm.Expr("up + ?", traffic.Up),
|
||||||
"down": gorm.Expr("down + ?", traffic.Down),
|
"down": gorm.Expr("down + ?", traffic.Down),
|
||||||
}).Error
|
}).Error
|
||||||
@@ -893,13 +893,13 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for inbound_index := range inbounds {
|
for inbound_index := range inbounds {
|
||||||
settings := map[string]interface{}{}
|
settings := map[string]any{}
|
||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]any)
|
||||||
if ok {
|
if ok {
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
for traffic_index := range dbClientTraffics {
|
for traffic_index := range dbClientTraffics {
|
||||||
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||||
oldExpiryTime := c["expiryTime"].(float64)
|
oldExpiryTime := c["expiryTime"].(float64)
|
||||||
@@ -909,7 +909,7 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
@@ -951,7 +951,7 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
|||||||
var clientsToAdd []struct {
|
var clientsToAdd []struct {
|
||||||
protocol string
|
protocol string
|
||||||
tag string
|
tag string
|
||||||
client map[string]interface{}
|
client map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
@@ -962,11 +962,11 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
|||||||
return false, 0, err
|
return false, 0, err
|
||||||
}
|
}
|
||||||
for inbound_index := range inbounds {
|
for inbound_index := range inbounds {
|
||||||
settings := map[string]interface{}{}
|
settings := map[string]any{}
|
||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
for traffic_index, traffic := range traffics {
|
for traffic_index, traffic := range traffics {
|
||||||
if traffic.Email == c["email"].(string) {
|
if traffic.Email == c["email"].(string) {
|
||||||
newExpiryTime := traffic.ExpiryTime
|
newExpiryTime := traffic.ExpiryTime
|
||||||
@@ -983,14 +983,14 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
|||||||
struct {
|
struct {
|
||||||
protocol string
|
protocol string
|
||||||
tag string
|
tag string
|
||||||
client map[string]interface{}
|
client map[string]any
|
||||||
}{
|
}{
|
||||||
protocol: string(inbounds[inbound_index].Protocol),
|
protocol: string(inbounds[inbound_index].Protocol),
|
||||||
tag: inbounds[inbound_index].Tag,
|
tag: inbounds[inbound_index].Tag,
|
||||||
client: c,
|
client: c,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
clients[client_index] = interface{}(c)
|
clients[client_index] = any(c)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1147,7 +1147,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||||
result := tx.Model(xray.ClientTraffic{}).
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]any{
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
@@ -1258,18 +1258,18 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
|
|||||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
if c["email"] == clientEmail {
|
if c["email"] == clientEmail {
|
||||||
c["tgId"] = tgId
|
c["tgId"] = tgId
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
@@ -1343,18 +1343,18 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
|
|||||||
return false, false, common.NewError("Client Not Found For Email:", clientEmail)
|
return false, false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false, err
|
return false, false, err
|
||||||
}
|
}
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
if c["email"] == clientEmail {
|
if c["email"] == clientEmail {
|
||||||
c["enable"] = !clientOldEnabled
|
c["enable"] = !clientOldEnabled
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
@@ -1405,18 +1405,18 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
|||||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
if c["email"] == clientEmail {
|
if c["email"] == clientEmail {
|
||||||
c["limitIp"] = count
|
c["limitIp"] = count
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
@@ -1462,18 +1462,18 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
|||||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
if c["email"] == clientEmail {
|
if c["email"] == clientEmail {
|
||||||
c["expiryTime"] = expiry_time
|
c["expiryTime"] = expiry_time
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
@@ -1522,18 +1522,18 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
|||||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
clients := settings["clients"].([]interface{})
|
clients := settings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
if c["email"] == clientEmail {
|
if c["email"] == clientEmail {
|
||||||
c["totalGB"] = totalGB * 1024 * 1024 * 1024
|
c["totalGB"] = totalGB * 1024 * 1024 * 1024
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
@@ -1551,7 +1551,7 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
|||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("email = ?", clientEmail).
|
Where("email = ?", clientEmail).
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1582,14 +1582,14 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
|||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
cipher := ""
|
cipher := ""
|
||||||
if string(inbound.Protocol) == "shadowsocks" {
|
if string(inbound.Protocol) == "shadowsocks" {
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]any
|
||||||
err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
cipher = oldSettings["method"].(string)
|
cipher = oldSettings["method"].(string)
|
||||||
}
|
}
|
||||||
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
|
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]any{
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"id": client.ID,
|
"id": client.ID,
|
||||||
"security": client.Security,
|
"security": client.Security,
|
||||||
@@ -1634,7 +1634,7 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
|
|||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where(whereText, id).
|
Where(whereText, id).
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
return err
|
return err
|
||||||
@@ -1645,7 +1645,7 @@ func (s *InboundService) ResetAllTraffics() error {
|
|||||||
|
|
||||||
result := db.Model(model.Inbound{}).
|
result := db.Model(model.Inbound{}).
|
||||||
Where("user_id > ?", 0).
|
Where("user_id > ?", 0).
|
||||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
Updates(map[string]any{"up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
return err
|
return err
|
||||||
@@ -1681,17 +1681,17 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var oldSettings map[string]interface{}
|
var oldSettings map[string]any
|
||||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients := oldSettings["clients"].([]interface{})
|
oldClients := oldSettings["clients"].([]any)
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for _, client := range oldClients {
|
for _, client := range oldClients {
|
||||||
deplete := false
|
deplete := false
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]any)
|
||||||
for _, email := range emails {
|
for _, email := range emails {
|
||||||
if email == c["email"].(string) {
|
if email == c["email"].(string) {
|
||||||
deplete = true
|
deplete = true
|
||||||
@@ -1907,14 +1907,14 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for inbound_index := range inbounds {
|
for inbound_index := range inbounds {
|
||||||
settings := map[string]interface{}{}
|
settings := map[string]any{}
|
||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]any)
|
||||||
if ok {
|
if ok {
|
||||||
// Fix Client configuration problems
|
// Fix Client configuration problems
|
||||||
var newClients []interface{}
|
var newClients []any
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]any)
|
||||||
|
|
||||||
// Add email='' if it is not exists
|
// Add email='' if it is not exists
|
||||||
if _, ok := c["email"]; !ok {
|
if _, ok := c["email"]; !ok {
|
||||||
@@ -1923,7 +1923,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
|
|
||||||
// Convert string tgId to int64
|
// Convert string tgId to int64
|
||||||
if _, ok := c["tgId"]; ok {
|
if _, ok := c["tgId"]; ok {
|
||||||
var tgId interface{} = c["tgId"]
|
var tgId any = c["tgId"]
|
||||||
if tgIdStr, ok2 := tgId.(string); ok2 {
|
if tgIdStr, ok2 := tgId.(string); ok2 {
|
||||||
tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64)
|
tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1938,7 +1938,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
c["flow"] = ""
|
c["flow"] = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClients = append(newClients, interface{}(c))
|
newClients = append(newClients, any(c))
|
||||||
}
|
}
|
||||||
settings["clients"] = newClients
|
settings["clients"] = newClients
|
||||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
@@ -1985,14 +1985,14 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, ep := range externalProxy {
|
for _, ep := range externalProxy {
|
||||||
var reverses interface{}
|
var reverses any
|
||||||
var stream map[string]interface{}
|
var stream map[string]any
|
||||||
json.Unmarshal(ep.StreamSettings, &stream)
|
json.Unmarshal(ep.StreamSettings, &stream)
|
||||||
if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok {
|
if tlsSettings, ok := stream["tlsSettings"].(map[string]any); ok {
|
||||||
if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok {
|
if settings, ok := tlsSettings["settings"].(map[string]any); ok {
|
||||||
if domains, ok := settings["domains"].([]interface{}); ok {
|
if domains, ok := settings["domains"].([]any); ok {
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domainMap, ok := domain.(map[string]interface{}); ok {
|
if domainMap, ok := domain.(map[string]any); ok {
|
||||||
domainMap["forceTls"] = "same"
|
domainMap["forceTls"] = "same"
|
||||||
domainMap["port"] = ep.Port
|
domainMap["port"] = ep.Port
|
||||||
domainMap["dest"] = domainMap["domain"].(string)
|
domainMap["dest"] = domainMap["domain"].(string)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (s *OutboundService) ResetOutboundTraffic(tag string) error {
|
|||||||
|
|
||||||
result := db.Model(model.OutboundTraffics{}).
|
result := db.Model(model.OutboundTraffics{}).
|
||||||
Where(whereText, tag).
|
Where(whereText, tag).
|
||||||
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
|
Updates(map[string]any{"up": 0, "down": 0, "total": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ type Release struct {
|
|||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
xrayService XrayService
|
xrayService XrayService
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
|
cachedIPv4 string
|
||||||
|
cachedIPv6 string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublicIP(url string) string {
|
func getPublicIP(url string) string {
|
||||||
@@ -120,6 +122,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
T: now,
|
T: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CPU stats
|
||||||
percents, err := cpu.Percent(0, false)
|
percents, err := cpu.Percent(0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get cpu percent failed:", err)
|
logger.Warning("get cpu percent failed:", err)
|
||||||
@@ -133,22 +136,17 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status.LogicalPro = runtime.NumCPU()
|
status.LogicalPro = runtime.NumCPU()
|
||||||
if p != nil && p.IsRunning() {
|
|
||||||
status.AppStats.Uptime = p.GetUptime()
|
|
||||||
} else {
|
|
||||||
status.AppStats.Uptime = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuInfos, err := cpu.Info()
|
cpuInfos, err := cpu.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get cpu info failed:", err)
|
logger.Warning("get cpu info failed:", err)
|
||||||
} else if len(cpuInfos) > 0 {
|
} else if len(cpuInfos) > 0 {
|
||||||
cpuInfo := cpuInfos[0]
|
status.CpuSpeedMhz = cpuInfos[0].Mhz
|
||||||
status.CpuSpeedMhz = cpuInfo.Mhz // setting CPU speed in MHz
|
|
||||||
} else {
|
} else {
|
||||||
logger.Warning("could not find cpu info")
|
logger.Warning("could not find cpu info")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uptime
|
||||||
upTime, err := host.Uptime()
|
upTime, err := host.Uptime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get uptime failed:", err)
|
logger.Warning("get uptime failed:", err)
|
||||||
@@ -156,6 +154,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Uptime = upTime
|
status.Uptime = upTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memory stats
|
||||||
memInfo, err := mem.VirtualMemory()
|
memInfo, err := mem.VirtualMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get virtual memory failed:", err)
|
logger.Warning("get virtual memory failed:", err)
|
||||||
@@ -172,14 +171,16 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Swap.Total = swapInfo.Total
|
status.Swap.Total = swapInfo.Total
|
||||||
}
|
}
|
||||||
|
|
||||||
distInfo, err := disk.Usage("/")
|
// Disk stats
|
||||||
|
diskInfo, err := disk.Usage("/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get dist usage failed:", err)
|
logger.Warning("get disk usage failed:", err)
|
||||||
} else {
|
} else {
|
||||||
status.Disk.Current = distInfo.Used
|
status.Disk.Current = diskInfo.Used
|
||||||
status.Disk.Total = distInfo.Total
|
status.Disk.Total = diskInfo.Total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load averages
|
||||||
avgState, err := load.Avg()
|
avgState, err := load.Avg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get load avg failed:", err)
|
logger.Warning("get load avg failed:", err)
|
||||||
@@ -187,6 +188,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}
|
status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Network stats
|
||||||
ioStats, err := net.IOCounters(false)
|
ioStats, err := net.IOCounters(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get io counters failed:", err)
|
logger.Warning("get io counters failed:", err)
|
||||||
@@ -207,6 +209,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
logger.Warning("can not find io counters")
|
logger.Warning("can not find io counters")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TCP/UDP connections
|
||||||
status.TcpCount, err = sys.GetTCPCount()
|
status.TcpCount, err = sys.GetTCPCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get tcp connections failed:", err)
|
logger.Warning("get tcp connections failed:", err)
|
||||||
@@ -217,9 +220,15 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
logger.Warning("get udp connections failed:", err)
|
logger.Warning("get udp connections failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
status.PublicIP.IPv4 = getPublicIP("https://api.ipify.org")
|
// IP fetching with caching
|
||||||
status.PublicIP.IPv6 = getPublicIP("https://api6.ipify.org")
|
if s.cachedIPv4 == "" || s.cachedIPv6 == "" {
|
||||||
|
s.cachedIPv4 = getPublicIP("https://api.ipify.org")
|
||||||
|
s.cachedIPv6 = getPublicIP("https://api6.ipify.org")
|
||||||
|
}
|
||||||
|
status.PublicIP.IPv4 = s.cachedIPv4
|
||||||
|
status.PublicIP.IPv6 = s.cachedIPv6
|
||||||
|
|
||||||
|
// Xray status
|
||||||
if s.xrayService.IsXrayRunning() {
|
if s.xrayService.IsXrayRunning() {
|
||||||
status.Xray.State = Running
|
status.Xray.State = Running
|
||||||
status.Xray.ErrorMsg = ""
|
status.Xray.ErrorMsg = ""
|
||||||
@@ -233,9 +242,10 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
||||||
}
|
}
|
||||||
status.Xray.Version = s.xrayService.GetXrayVersion()
|
status.Xray.Version = s.xrayService.GetXrayVersion()
|
||||||
|
|
||||||
|
// Application stats
|
||||||
var rtm runtime.MemStats
|
var rtm runtime.MemStats
|
||||||
runtime.ReadMemStats(&rtm)
|
runtime.ReadMemStats(&rtm)
|
||||||
|
|
||||||
status.AppStats.Mem = rtm.Sys
|
status.AppStats.Mem = rtm.Sys
|
||||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||||
if p != nil && p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
@@ -440,7 +450,7 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
func (s *ServerService) GetConfigJson() (any, error) {
|
||||||
config, err := s.xrayService.GetXrayConfig()
|
config, err := s.xrayService.GetXrayConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -450,7 +460,7 @@ func (s *ServerService) GetConfigJson() (interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonData interface{}
|
var jsonData any
|
||||||
err = json.Unmarshal(contents, &jsonData)
|
err = json.Unmarshal(contents, &jsonData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -581,7 +591,7 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
func (s *ServerService) GetNewX25519Cert() (any, error) {
|
||||||
// Run the command
|
// Run the command
|
||||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
@@ -599,7 +609,7 @@ func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
|||||||
privateKey := strings.TrimSpace(privateKeyLine[1])
|
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||||
publicKey := strings.TrimSpace(publicKeyLine[1])
|
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||||
|
|
||||||
keyPair := map[string]interface{}{
|
keyPair := map[string]any{
|
||||||
"privateKey": privateKey,
|
"privateKey": privateKey,
|
||||||
"publicKey": publicKey,
|
"publicKey": publicKey,
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user