Compare commits
265 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aff9d0ea15 | ||
|
|
526426e2dd | ||
|
|
2223a21cfc | ||
|
|
47ccc7b501 | ||
|
|
c38e1e0cfe | ||
|
|
f36034541e | ||
|
|
783fa856c3 | ||
|
|
6139effb8a | ||
|
|
8ae7f4a564 | ||
|
|
6f46f3e636 | ||
|
|
ba278a4269 | ||
|
|
3825e36ef7 | ||
|
|
f6e0e1b3cf | ||
|
|
769590d779 | ||
|
|
1fa9101b40 | ||
|
|
3f2e1aede9 | ||
|
|
235e6880c1 | ||
|
|
ffa23a43c6 | ||
|
|
66e3c505e3 | ||
|
|
0938519f49 | ||
|
|
5f489c3d08 | ||
|
|
f82d0051b2 | ||
|
|
76267b23a0 | ||
|
|
40a926a54a | ||
|
|
3a835fbeb8 | ||
|
|
f6461b8386 | ||
|
|
586663c840 | ||
|
|
82ead05093 | ||
|
|
d9b1b200ce | ||
|
|
786a3ac992 | ||
|
|
8c5648eb09 | ||
|
|
4dfe527f20 | ||
|
|
980ebd99ca | ||
|
|
9b7cddbec7 | ||
|
|
2bd706aa92 | ||
|
|
95e0d9a468 | ||
|
|
4865754b3d | ||
|
|
92eaff9608 | ||
|
|
0b7aa8a9e0 | ||
|
|
678962d4ca | ||
|
|
795835c54f | ||
|
|
91360a3f49 | ||
|
|
4831c2f1b2 | ||
|
|
3166d497f9 | ||
|
|
f50ccce9ec | ||
|
|
c7e300f14d | ||
|
|
419a1938ee | ||
|
|
a48745cb3e | ||
|
|
ac9408c37f | ||
|
|
3d71289075 | ||
|
|
fbd3772788 | ||
|
|
334b28cddc | ||
|
|
2fbfc88bc1 | ||
|
|
f781979d38 | ||
|
|
aba37be6eb | ||
|
|
30042bc047 | ||
|
|
49f60f7775 | ||
|
|
5f44c80cd5 | ||
|
|
85d42ce94f | ||
|
|
2f3c3d0ed2 | ||
|
|
7debf96610 | ||
|
|
d8b60c3cd5 | ||
|
|
f8eb548376 | ||
|
|
88fc4f81d4 | ||
|
|
911f2b0bb5 | ||
|
|
2d16eabc6e | ||
|
|
be50be75fe | ||
|
|
70e7987df5 | ||
|
|
8ceeb454ee | ||
|
|
f311bf1dbf | ||
|
|
837d7f77a1 | ||
|
|
d0213ce50b | ||
|
|
622e440366 | ||
|
|
1dc5452f1d | ||
|
|
a0daf2fae2 | ||
|
|
a21bdc9396 | ||
|
|
4cf7f75749 | ||
|
|
bcdac5aad6 | ||
|
|
3d5f851fce | ||
|
|
5487dc41cc | ||
|
|
5a908b9f58 | ||
|
|
61288db11e | ||
|
|
317f7fe9da | ||
|
|
7b5dd2d0ee | ||
|
|
b1302c70fb | ||
|
|
addedb1adf | ||
|
|
62bb42cfab | ||
|
|
f4be9f234a | ||
|
|
947129a62a | ||
|
|
66f0a13145 | ||
|
|
9626379731 | ||
|
|
c2c61cdd5b | ||
|
|
b5ae580d12 | ||
|
|
63939244a4 | ||
|
|
213b693bd3 | ||
|
|
a289ef5d10 | ||
|
|
955eb8f142 | ||
|
|
d396fb5d06 | ||
|
|
b5dd258074 | ||
|
|
c855a292cb | ||
|
|
f2132c62e9 | ||
|
|
94a3807353 | ||
|
|
7cacfc074e | ||
|
|
9e8ac8a087 | ||
|
|
e64a9eeee6 | ||
|
|
a55a1a7102 | ||
|
|
46bc39c160 | ||
|
|
2a182d8b9a | ||
|
|
77241c7fcf | ||
|
|
fd6a85afd9 | ||
|
|
9a89d7bfab | ||
|
|
edd6b22109 | ||
|
|
5468069bef | ||
|
|
0cce35784e | ||
|
|
80c1e58ed5 | ||
|
|
b0871a6ef6 | ||
|
|
288374d5fa | ||
|
|
1f7c79c735 | ||
|
|
251fd608df | ||
|
|
456941323b | ||
|
|
a6a77688dc | ||
|
|
09cd2248dc | ||
|
|
8143379645 | ||
|
|
5bd6baa055 | ||
|
|
41e9290574 | ||
|
|
cf7d50617b | ||
|
|
95e006963c | ||
|
|
65588a4492 | ||
|
|
d39c7e4ae3 | ||
|
|
3bec9ee273 | ||
|
|
7b3628d33b | ||
|
|
ad1aa5b2f9 | ||
|
|
46ef8c503e | ||
|
|
721fec3b5a | ||
|
|
30a5f66f26 | ||
|
|
bb6e6861ca | ||
|
|
4c0e391597 | ||
|
|
43c1fc9aad | ||
|
|
7a48cbb191 | ||
|
|
004d69392b | ||
|
|
fc0882805d | ||
|
|
f553922d53 | ||
|
|
7b2764566c | ||
|
|
55d38dfa48 | ||
|
|
0e266b88f0 | ||
|
|
7bb3e517b2 | ||
|
|
7d0c3b6517 | ||
|
|
67201fc678 | ||
|
|
d137deccfa | ||
|
|
00777e3a25 | ||
|
|
bcb2f125ff | ||
|
|
37ab8f42e9 | ||
|
|
cf1cfbee96 | ||
|
|
d89e03023f | ||
|
|
5ec1559c7b | ||
|
|
cf2b1fd9ec | ||
|
|
ddbc1602ce | ||
|
|
77cee098ad | ||
|
|
89b94c2c90 | ||
|
|
350743fea3 | ||
|
|
8011d0b6c6 | ||
|
|
9e5d7ac1d0 | ||
|
|
e01fb9b605 | ||
|
|
52a468d586 | ||
|
|
c73c71cc83 | ||
|
|
2141d62069 | ||
|
|
f286c9a86a | ||
|
|
b4fd254c71 | ||
|
|
0a8cf7e41b | ||
|
|
abd48551fd | ||
|
|
bb9a10051f | ||
|
|
5c6406ab58 | ||
|
|
a25137f215 | ||
|
|
50c296eb28 | ||
|
|
b1a302de95 | ||
|
|
7b567458ff | ||
|
|
13e3e23f20 | ||
|
|
efecdf5fd0 | ||
|
|
f21904caf2 | ||
|
|
15a97af215 | ||
|
|
20a55c086e | ||
|
|
c727b81772 | ||
|
|
77692e0298 | ||
|
|
5e3e965647 | ||
|
|
88cde18bb2 | ||
|
|
7fd93e25fd | ||
|
|
1c9643e6a3 | ||
|
|
eb83516fa5 | ||
|
|
1b8df3c0a1 | ||
|
|
54d45cc029 | ||
|
|
e35767aff2 | ||
|
|
9bbcb74db6 | ||
|
|
515e7f7fef | ||
|
|
87a5190b7d | ||
|
|
9d47d74a7a | ||
|
|
92f5dfed1c | ||
|
|
867e5ea022 | ||
|
|
a24814104c | ||
|
|
024b65524d | ||
|
|
f22dd6b53d | ||
|
|
735df6bd4e | ||
|
|
0e77547e98 | ||
|
|
747a1e1f60 | ||
|
|
ac31d6d9fb | ||
|
|
83c853ffb6 | ||
|
|
058ab5f901 | ||
|
|
78638a9737 | ||
|
|
7f8f0b0f2d | ||
|
|
7b9e0b946e | ||
|
|
6c087ceb1a | ||
|
|
6602c55f3c | ||
|
|
d405141ad0 | ||
|
|
3ee3432d8f | ||
|
|
b2d70a2a9b | ||
|
|
26f160fb89 | ||
|
|
0a5811adf8 | ||
|
|
733a011b28 | ||
|
|
c8023b7c8d | ||
|
|
bed3cd445d | ||
|
|
c8baf5ceee | ||
|
|
85c715a2f6 | ||
|
|
55c1fe26fb | ||
|
|
8d11f83ac7 | ||
|
|
d349bffcd6 | ||
|
|
5856160c30 | ||
|
|
3cd3693b2c | ||
|
|
bc3003be54 | ||
|
|
e53615d119 | ||
|
|
55232f9033 | ||
|
|
fd40e97008 | ||
|
|
1598ad804d | ||
|
|
c8e666d8ae | ||
|
|
e1533b9418 | ||
|
|
a60a8d8a2f | ||
|
|
73704e38d5 | ||
|
|
1680bb36c3 | ||
|
|
cd483c191a | ||
|
|
7d09b4e840 | ||
|
|
83ffa25d6f | ||
|
|
a53d2b927f | ||
|
|
146dc6ce4a | ||
|
|
e597ea5ab2 | ||
|
|
3e833fca9b | ||
|
|
c96cf85619 | ||
|
|
0a63c75138 | ||
|
|
c6295085fe | ||
|
|
4b3bdebfa5 | ||
|
|
1a603b2501 | ||
|
|
3fa5f834b8 | ||
|
|
ff33539fba | ||
|
|
961636b510 | ||
|
|
88b7d0dc44 | ||
|
|
e164c7e780 | ||
|
|
481d4beabb | ||
|
|
19c991014e | ||
|
|
12ec487241 | ||
|
|
a18cbdcf11 | ||
|
|
4f8de18d1f | ||
|
|
dbac48f05d | ||
|
|
3a02359325 | ||
|
|
9e63b0e2b3 | ||
|
|
07dc8c4803 | ||
|
|
20bfd71cf1 | ||
|
|
6a33a48a9a | ||
|
|
1885a8c0bf |
41
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Release X-ui dockerhub
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out the code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.1.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2.1.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4.4.0
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v4.0.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64, linux/arm64/v8
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
4
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4.0.0
|
uses: actions/setup-go@v4.0.1
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- name: build linux amd64 version
|
- name: build linux amd64 version
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4.0.0
|
uses: actions/setup-go@v4.0.1
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
- name: build linux arm64 version
|
- name: build linux arm64 version
|
||||||
|
|||||||
17
.gitignore
vendored
@@ -1,14 +1,15 @@
|
|||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
|
.cache
|
||||||
|
.sync*
|
||||||
|
*.tar.gz
|
||||||
|
access.log
|
||||||
|
error.log
|
||||||
tmp
|
tmp
|
||||||
|
main
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
x-ui-*.tar.gz
|
|
||||||
/x-ui
|
|
||||||
/release.sh
|
|
||||||
.sync*
|
|
||||||
main
|
|
||||||
release/
|
release/
|
||||||
access.log
|
/release.sh
|
||||||
error.log
|
/x-ui
|
||||||
.cache
|
|
||||||
|
|||||||
22
DockerInit.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
if [ $1 == "amd64" ]; then
|
||||||
|
ARCH="64";
|
||||||
|
FNAME="amd64";
|
||||||
|
elif [ $1 == "arm64" ]; then
|
||||||
|
ARCH="arm64-v8a"
|
||||||
|
FNAME="arm64";
|
||||||
|
else
|
||||||
|
ARCH="64";
|
||||||
|
FNAME="amd64";
|
||||||
|
fi
|
||||||
|
mkdir -p build/bin
|
||||||
|
cd build/bin
|
||||||
|
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
|
||||||
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
||||||
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||||
|
|
||||||
|
cd ../../
|
||||||
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#Build latest x-ui from source
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||||
|
COPY . .
|
||||||
|
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
|
||||||
|
RUN ./DockerInit.sh "$TARGETARCH"
|
||||||
|
|
||||||
|
|
||||||
|
#Build app image using latest x-ui
|
||||||
|
FROM alpine
|
||||||
|
ENV TZ=Asia/Tehran
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add ca-certificates tzdata
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/ /app/
|
||||||
|
VOLUME [ "/etc/x-ui" ]
|
||||||
|
ENTRYPOINT [ "/app/x-ui" ]
|
||||||
66
README.md
@@ -1,16 +1,20 @@
|
|||||||
# 3x-ui
|
# 3x-ui
|
||||||
|
|
||||||
|
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](#)
|
[](#)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
**Buy Me a Coffee :**
|
||||||
|
|
||||||
|
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
# Install & Upgrade
|
# Install & Upgrade
|
||||||
|
|
||||||
@@ -20,10 +24,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## Install custom version
|
## Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.3.3`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.6.0`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.3.3
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.6.0
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
@@ -33,8 +37,8 @@ apt-get install certbot -y
|
|||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
certbot renew --dry-run
|
certbot renew --dry-run
|
||||||
```
|
```
|
||||||
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
|
||||||
|
|
||||||
|
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
|
|
||||||
@@ -45,12 +49,12 @@ or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
|||||||
|
|
||||||
Before you set ssl on settings
|
Before you set ssl on settings
|
||||||
|
|
||||||
- http://ip:2053/xui
|
- http://ip:2053/panel
|
||||||
- http://domain:2053/xui
|
- http://domain:2053/panel
|
||||||
|
|
||||||
After you set ssl on settings
|
After you set ssl on settings
|
||||||
|
|
||||||
- https://yourdomain:2053/xui
|
- https://yourdomain:2053/panel
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
@@ -67,6 +71,31 @@ Example:
|
|||||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Install with Docker
|
||||||
|
|
||||||
|
1. Install Docker:
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
2. Run 3x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
# Xray Configurations:
|
# Xray Configurations:
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
@@ -116,6 +145,7 @@ If you want to use routing to WARP follow steps as below:
|
|||||||
- For more advanced configuration items, please refer to the panel
|
- For more advanced configuration items, please refer to the panel
|
||||||
- Fix api routes (user setting will create with api)
|
- Fix api routes (user setting will create with api)
|
||||||
- Support to change configs by different items provided in panel
|
- Support to change configs by different items provided in panel
|
||||||
|
- Support export/import database from panel
|
||||||
|
|
||||||
# Tg robot use
|
# Tg robot use
|
||||||
|
|
||||||
@@ -157,26 +187,33 @@ Reference syntax:
|
|||||||
## API routes
|
## API routes
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
||||||
- `/xui/API/inbounds` base for following actions:
|
- `/panel/api/inbounds` base for following actions:
|
||||||
|
|
||||||
| Method | Path | Action |
|
| Method | Path | Action |
|
||||||
| :----: | ---------------------------------- | ------------------------------------------- |
|
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||||
| `GET` | `"/list"` | Get all inbounds |
|
| `GET` | `"/list"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
||||||
|
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||||
| `POST` | `"/add"` | Add inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||||
| `POST` | `"/update/:id"` | Update Inbound |
|
| `POST` | `"/update/:id"` | Update Inbound |
|
||||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||||
| `POST` | `"/addClient"` | Add Client to inbound |
|
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by UID/Password as clientId |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update Client by UID/Password as clientId |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
|
- `client.id` for VMESS and VLESS
|
||||||
|
- `client.password` for TROJAN
|
||||||
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
@@ -190,17 +227,14 @@ Reference syntax:
|
|||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
|
||||||
# Buy Me a Coffee
|
|
||||||
|
|
||||||
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
|
|
||||||
|
|
||||||
# Pictures
|
# Pictures
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.3.4
|
1.6.0
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -15,7 +17,16 @@ import (
|
|||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
|
var initializers = []func() error{
|
||||||
|
initUser,
|
||||||
|
initInbound,
|
||||||
|
initSetting,
|
||||||
|
initInboundClientIps,
|
||||||
|
initClientTraffic,
|
||||||
|
}
|
||||||
|
|
||||||
func initUser() error {
|
func initUser() error {
|
||||||
|
|
||||||
err := db.AutoMigrate(&model.User{})
|
err := db.AutoMigrate(&model.User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -52,7 +63,7 @@ func initClientTraffic() error {
|
|||||||
|
|
||||||
func InitDB(dbPath string) error {
|
func InitDB(dbPath string) error {
|
||||||
dir := path.Dir(dbPath)
|
dir := path.Dir(dbPath)
|
||||||
err := os.MkdirAll(dir, fs.ModeDir)
|
err := os.MkdirAll(dir, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -73,25 +84,10 @@ func InitDB(dbPath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initUser()
|
for _, initialize := range initializers {
|
||||||
if err != nil {
|
if err := initialize(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = initInbound()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initSetting()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initInboundClientIps()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initClientTraffic()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -104,3 +100,13 @@ func GetDB() *gorm.DB {
|
|||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
return err == gorm.ErrRecordNotFound
|
return err == gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
||||||
|
signature := []byte("SQLite format 3\x00")
|
||||||
|
buf := make([]byte, len(signature))
|
||||||
|
_, err := file.ReadAt(buf, 0)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return bytes.Equal(buf, signature), nil
|
||||||
|
}
|
||||||
|
|||||||
16
docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
3x-ui:
|
||||||
|
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
container_name: 3x-ui
|
||||||
|
hostname: yourhostname
|
||||||
|
volumes:
|
||||||
|
- $PWD/db/:/etc/x-ui/
|
||||||
|
- $PWD/cert/:/root/cert/
|
||||||
|
environment:
|
||||||
|
XRAY_VMESS_AEAD_FORCED: "false"
|
||||||
|
tty: true
|
||||||
|
network_mode: host
|
||||||
|
restart: unless-stopped
|
||||||
39
go.mod
@@ -3,34 +3,37 @@ module x-ui
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Workiva/go-datastructures v1.1.0
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.0
|
||||||
github.com/go-cmd/cmd v1.4.1
|
github.com/go-cmd/cmd v1.4.1
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
|
github.com/mymmrac/telego v0.24.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3
|
github.com/shirou/gopsutil/v3 v3.23.4
|
||||||
github.com/xtls/xray-core v1.8.1
|
github.com/xtls/xray-core v1.8.1
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.9.0
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.55.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.1
|
||||||
gorm.io/gorm v1.25.0
|
gorm.io/gorm v1.25.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/bytedance/sonic v1.8.8 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/bytedance/sonic v1.8.9 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/fasthttp/router v1.4.19 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // 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.12.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
@@ -38,25 +41,29 @@ require (
|
|||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.16.5 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/leodido/go-urn v1.2.3 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/mattn/go-sqlite3 v1.14.16 // 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/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.47.0 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.8.0 // indirect
|
golang.org/x/crypto v0.9.0 // indirect
|
||||||
golang.org/x/net v0.9.0 // indirect
|
golang.org/x/net v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
78
go.sum
@@ -1,16 +1,17 @@
|
|||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
||||||
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q=
|
github.com/bytedance/sonic v1.8.9 h1:mXB6OoHaI9OrWugkvNxWiuHTy5RCrVfxg2Nn40sf0oc=
|
||||||
github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.8.9/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
@@ -18,7 +19,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
|
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
||||||
|
github.com/fasthttp/router v1.4.19/go.mod h1:+Fh3YOd8x1+he6ZS+d2iUDBH9MGGZ1xQFUor0DE9rKE=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
||||||
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/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||||
@@ -42,11 +47,9 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
|||||||
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.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
|
||||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
@@ -81,20 +84,20 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||||
|
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
@@ -105,6 +108,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mymmrac/telego v0.24.0 h1:0fd+v2/dToL6/DtsnWr+2saK7ZxIgLY+LI9kqJQbPEo=
|
||||||
|
github.com/mymmrac/telego v0.24.0/go.mod h1:y557P/iMHSaOVDi5Nmy1gNelqrw+jaBMvP9guPaNJsQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||||
@@ -133,14 +138,16 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
|||||||
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
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=
|
||||||
@@ -165,16 +172,21 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
|||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
|
||||||
|
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
||||||
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
||||||
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
@@ -182,8 +194,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@@ -193,8 +205,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -212,8 +224,9 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -235,8 +248,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
@@ -250,11 +263,10 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
gorm.io/driver/sqlite v1.5.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4=
|
||||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
gorm.io/driver/sqlite v1.5.1/go.mod h1:7MZZ2Z8bqyfSQA1gYEV6MagQWj3cpUkJj9Z+d1HEMEQ=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
||||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
22
install.sh
@@ -25,9 +25,9 @@ echo "The OS release is: $release"
|
|||||||
|
|
||||||
arch3xui() {
|
arch3xui() {
|
||||||
case "$(uname -m)" in
|
case "$(uname -m)" in
|
||||||
x86_64 | x64 | amd64 ) echo 'amd64' ;;
|
x86_64 | x64 | amd64) echo 'amd64' ;;
|
||||||
armv8 | arm64 | aarch64 ) echo 'arm64' ;;
|
armv8 | arm64 | aarch64) echo 'arm64' ;;
|
||||||
* ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
echo "arch: $(arch3xui)"
|
echo "arch: $(arch3xui)"
|
||||||
@@ -39,7 +39,7 @@ if [[ "${release}" == "centos" ]]; then
|
|||||||
if [[ ${os_version} -lt 8 ]]; then
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
if [[ ${os_version} -lt 20 ]]; then
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
@@ -59,18 +59,17 @@ fi
|
|||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos|fedora)
|
centos | fedora)
|
||||||
yum install -y -q wget curl tar
|
yum install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt install -y -q wget curl tar
|
apt install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
/usr/local/x-ui/x-ui migrate
|
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||||
@@ -101,6 +100,7 @@ config_after_install() {
|
|||||||
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
/usr/local/x-ui/x-ui migrate
|
||||||
}
|
}
|
||||||
|
|
||||||
install_x-ui() {
|
install_x-ui() {
|
||||||
|
|||||||
@@ -2,17 +2,28 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var (
|
||||||
|
logger *logging.Logger
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
if logger != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
format := logging.MustStringFormatter(
|
format := logging.MustStringFormatter(
|
||||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||||
)
|
)
|
||||||
@@ -21,39 +32,55 @@ func InitLogger(level logging.Level) {
|
|||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||||
backendLeveled.SetLevel(level, "")
|
backendLeveled.SetLevel(level, "")
|
||||||
newLogger.SetBackend(backendLeveled)
|
newLogger.SetBackend(logging.MultiLogger(backendLeveled))
|
||||||
|
|
||||||
logger = newLogger
|
logger = newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
if logger != nil {
|
||||||
|
logger.Debug(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
logger.Debugf(format, args...)
|
if logger != nil {
|
||||||
|
logger.Debugf(format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(args ...interface{}) {
|
func Info(args ...interface{}) {
|
||||||
logger.Info(args...)
|
if logger != nil {
|
||||||
|
logger.Info(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...interface{}) {
|
||||||
logger.Infof(format, args...)
|
if logger != nil {
|
||||||
|
logger.Infof(format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
if logger != nil {
|
||||||
|
logger.Warning(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...interface{}) {
|
||||||
logger.Warningf(format, args...)
|
if logger != nil {
|
||||||
|
logger.Warningf(format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(args ...interface{}) {
|
func Error(args ...interface{}) {
|
||||||
logger.Error(args...)
|
if logger != nil {
|
||||||
|
logger.Error(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...interface{}) {
|
||||||
logger.Errorf(format, args...)
|
if logger != nil {
|
||||||
|
logger.Errorf(format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/sub"
|
||||||
"x-ui/v2ui"
|
"x-ui/v2ui"
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
@@ -50,6 +51,16 @@ func runWebServer() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subServer *sub.Server
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
// Trap shutdown signals
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||||
@@ -62,6 +73,11 @@ func runWebServer() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop server err:", err)
|
logger.Warning("stop server err:", err)
|
||||||
}
|
}
|
||||||
|
err = subServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop server err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
@@ -69,8 +85,18 @@ func runWebServer() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
server.Stop()
|
server.Stop()
|
||||||
|
subServer.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +159,6 @@ func updateTgbotEnableSts(status bool) {
|
|||||||
logger.Infof("SetTgbotenabled[%v] success", status)
|
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||||
@@ -212,8 +237,7 @@ func migrateDb() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
fmt.Println("Start migrating database...")
|
fmt.Println("Start migrating database...")
|
||||||
inboundService.MigrationRequirements()
|
inboundService.MigrateDB()
|
||||||
inboundService.RemoveOrphanedTraffics()
|
|
||||||
fmt.Println("Migration done!")
|
fmt.Println("Migration done!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
media/1.png
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 58 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 37 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 99 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 23 KiB |
BIN
media/5.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
media/6.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -54,35 +58,41 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"ext:iran.dat:ads"
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "IPv4",
|
"outboundTag": "IPv4",
|
||||||
"domain": ["geosite:google"]
|
"domain": [
|
||||||
|
"geosite:google"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -59,40 +63,44 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"ext:iran.dat:ads"
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "WARP",
|
"outboundTag": "WARP",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:google",
|
|
||||||
"geosite:netflix",
|
|
||||||
"geosite:spotify",
|
"geosite:spotify",
|
||||||
"geosite:openai"
|
"geosite:netflix",
|
||||||
|
"geosite:openai",
|
||||||
|
"geosite:google"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,18 +51,24 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
@@ -73,4 +83,4 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,30 +51,27 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
},
|
"bittorrent"
|
||||||
{
|
]
|
||||||
"type": "field",
|
|
||||||
"outboundTag": "blocked",
|
|
||||||
"ip": ["geoip:private"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "field",
|
|
||||||
"outboundTag": "blocked",
|
|
||||||
"ip": ["geoip:ir"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
171
sub/sub.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/network"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
httpServer *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
|
||||||
|
sub *SUBController
|
||||||
|
settingService service.SettingService
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &Server{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
|
if config.IsDebug() {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
|
gin.DefaultWriter = io.Discard
|
||||||
|
gin.DefaultErrorWriter = io.Discard
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := gin.Default()
|
||||||
|
|
||||||
|
subPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if subDomain != "" {
|
||||||
|
validateDomain := func(c *gin.Context) {
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
|
||||||
|
if host != subDomain {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.Use(validateDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := engine.Group(subPath)
|
||||||
|
|
||||||
|
s.sub = NewSUBController(g)
|
||||||
|
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() (err error) {
|
||||||
|
//This is an anonymous function, no function name
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
subEnable, err := s.settingService.GetSubEnable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !subEnable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
engine, err := s.initRouter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile, err := s.settingService.GetSubCertFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyFile, err := s.settingService.GetSubKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listen, err := s.settingService.GetSubListen()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port, err := s.settingService.GetSubPort()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
||||||
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
listener.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
|
listener = tls.NewListener(listener, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
logger.Info("Sub server run https on", listener.Addr())
|
||||||
|
} else {
|
||||||
|
logger.Info("Sub server run http on", listener.Addr())
|
||||||
|
}
|
||||||
|
s.listener = listener
|
||||||
|
|
||||||
|
s.httpServer = &http.Server{
|
||||||
|
Handler: engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.httpServer.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() error {
|
||||||
|
s.cancel()
|
||||||
|
|
||||||
|
var err1 error
|
||||||
|
var err2 error
|
||||||
|
if s.httpServer != nil {
|
||||||
|
err1 = s.httpServer.Shutdown(s.ctx)
|
||||||
|
}
|
||||||
|
if s.listener != nil {
|
||||||
|
err2 = s.listener.Close()
|
||||||
|
}
|
||||||
|
return common.Combine(err1, err2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetCtx() context.Context {
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
package controller
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/web/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
BaseController
|
subService SubService
|
||||||
|
|
||||||
subService service.SubService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
@@ -21,7 +18,7 @@ func NewSUBController(g *gin.RouterGroup) *SUBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/sub")
|
g = g.Group("/")
|
||||||
|
|
||||||
g.GET("/:subid", a.subs)
|
g.GET("/:subid", a.subs)
|
||||||
}
|
}
|
||||||
@@ -29,7 +26,7 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, header, err := a.subService.GetSubs(subId, host)
|
subs, headers, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
@@ -38,8 +35,10 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
result += sub + "\n"
|
result += sub + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add subscription-userinfo
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||||
|
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||||
|
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||||
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -8,37 +8,53 @@ import (
|
|||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
inboundService InboundService
|
inboundService service.InboundService
|
||||||
|
settingServics service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) {
|
||||||
s.address = host
|
s.address = host
|
||||||
var result []string
|
var result []string
|
||||||
var header string
|
var headers []string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.getClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
if clients == nil {
|
if clients == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
|
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
||||||
|
if err == nil {
|
||||||
|
inbound.Listen = fallbackMaster.Listen
|
||||||
|
inbound.Port = fallbackMaster.Port
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
var masterStream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
||||||
|
stream["security"] = masterStream["security"]
|
||||||
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
|
inbound.StreamSettings = string(modifiedStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email)
|
||||||
result = append(result, link)
|
result = append(result, link)
|
||||||
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
@@ -66,15 +82,18 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
return result, header, nil
|
updateInterval, _ := s.settingServics.GetSubUpdates()
|
||||||
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
|
headers = append(headers, subId)
|
||||||
|
return result, headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
@@ -89,6 +108,19 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbound *model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).
|
||||||
|
Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
|
||||||
|
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||||
|
Find(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
@@ -97,6 +129,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
|||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email)
|
||||||
|
case "shadowsocks":
|
||||||
|
return s.genShadowsocksLink(inbound, email)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -105,9 +139,10 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
obj := map[string]interface{}{
|
obj := map[string]interface{}{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"ps": email,
|
"ps": remark,
|
||||||
"add": s.address,
|
"add": s.address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@@ -159,6 +194,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -181,6 +217,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
if serverName != "" {
|
if serverName != "" {
|
||||||
@@ -188,7 +227,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -199,6 +238,21 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
obj["aid"] = clients[clientIndex].AlterIds
|
obj["aid"] = clients[clientIndex].AlterIds
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
obj["ps"] = remark + "-" + domain["remark"].(string)
|
||||||
|
obj["add"] = domain["domain"].(string)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
@@ -210,7 +264,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
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
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -266,6 +320,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -290,6 +345,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -323,6 +381,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||||
|
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||||
|
params["spx"] = spx
|
||||||
|
}
|
||||||
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
address = sname
|
address = sname
|
||||||
@@ -357,6 +420,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -380,7 +446,22 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = email
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||||
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +472,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
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
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -447,6 +528,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -471,6 +553,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
@@ -500,6 +585,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||||
|
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||||
|
params["spx"] = spx
|
||||||
|
}
|
||||||
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
address = sname
|
address = sname
|
||||||
@@ -534,6 +624,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -558,10 +651,49 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = email
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = remark + "-" + domain["remark"].(string)
|
||||||
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
inboundPassword := settings["password"].(string)
|
||||||
|
method := settings["method"].(string)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, clients[clientIndex].Email)
|
||||||
|
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, remark)
|
||||||
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
switch val := data.(type) {
|
switch val := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var CtxDone = errors.New("context done")
|
|
||||||
|
|
||||||
func NewErrorf(format string, a ...interface{}) error {
|
func NewErrorf(format string, a ...interface{}) error {
|
||||||
msg := fmt.Sprintf(format, a...)
|
msg := fmt.Sprintf(format, a...)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import "sort"
|
|
||||||
|
|
||||||
func IsSubString(target string, str_array []string) bool {
|
|
||||||
sort.Strings(str_array)
|
|
||||||
index := sort.SearchStrings(str_array, target)
|
|
||||||
return index < len(str_array) && str_array[index] == target
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
func IsDone(ctx context.Context) bool {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
type RawMessage []byte
|
type RawMessage []byte
|
||||||
|
|
||||||
// MarshalJSON 自定义 json.RawMessage 默认行为
|
// MarshalJSON: Customize json.RawMessage default behavior
|
||||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
return []byte("null"), nil
|
return []byte("null"), nil
|
||||||
@@ -14,7 +14,7 @@ func (m RawMessage) MarshalJSON() ([]byte, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON sets *m to a copy of data.
|
// UnmarshalJSON: sets *m to a copy of data.
|
||||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ func getLinesNum(filename string) (int, error) {
|
|||||||
|
|
||||||
var buffPosition int
|
var buffPosition int
|
||||||
for {
|
for {
|
||||||
i := bytes.IndexByte(buf[buffPosition:], '\n')
|
i := bytes.IndexByte(buf[buffPosition:n], '\n')
|
||||||
if i < 0 || n == buffPosition {
|
if i < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
buffPosition += i + 1
|
buffPosition += i + 1
|
||||||
@@ -33,11 +33,12 @@ func getLinesNum(filename string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return sum, nil
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return sum, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return sum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
@@ -45,11 +46,11 @@ func GetTCPCount() (int, error) {
|
|||||||
|
|
||||||
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tcp4, err
|
return 0, err
|
||||||
}
|
}
|
||||||
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tcp4 + tcp6, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcp4 + tcp6, nil
|
return tcp4 + tcp6, nil
|
||||||
@@ -60,11 +61,11 @@ func GetUDPCount() (int, error) {
|
|||||||
|
|
||||||
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
|
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return udp4, err
|
return 0, err
|
||||||
}
|
}
|
||||||
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return udp4 + udp6, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return udp4 + udp6, nil
|
return udp4 + udp6, nil
|
||||||
|
|||||||
@@ -4,21 +4,27 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetConnectionCount(proto string) (int, error) {
|
||||||
stats, err := net.Connections("tcp")
|
if proto != "tcp" && proto != "udp" {
|
||||||
|
return 0, errors.New("invalid protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := net.Connections(proto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(stats), nil
|
return len(stats), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUDPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
stats, err := net.Connections("udp")
|
return GetConnectionCount("tcp")
|
||||||
if err != nil {
|
}
|
||||||
return 0, err
|
|
||||||
}
|
func GetUDPCount() (int, error) {
|
||||||
return len(stats), nil
|
return GetConnectionCount("udp")
|
||||||
}
|
}
|
||||||
|
|||||||
151
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
@@ -31,9 +31,9 @@ small{font-size:80%}
|
|||||||
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}
|
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}
|
||||||
sub{bottom:-.25em}
|
sub{bottom:-.25em}
|
||||||
sup{top:-.5em}
|
sup{top:-.5em}
|
||||||
a{color:#1890ff;text-decoration:none;background-color:transparent;outline:0;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}
|
a{color:rgb(0 150 112);text-decoration:none;background-color:transparent;outline:0;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}
|
||||||
a:hover{color:#40a9ff}
|
a:hover{color:rgb(0 150 112)}
|
||||||
a:active{color:#096dd9}
|
a:active{color:rgb(10, 105, 82)}
|
||||||
a:active,a:hover{text-decoration:none;outline:0}
|
a:active,a:hover{text-decoration:none;outline:0}
|
||||||
a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}
|
a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}
|
||||||
code,kbd,pre,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}
|
code,kbd,pre,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}
|
||||||
@@ -520,8 +520,8 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
|||||||
.ant-select-arrow .ant-select-arrow-icon{display:block}
|
.ant-select-arrow .ant-select-arrow-icon{display:block}
|
||||||
.ant-select-arrow .ant-select-arrow-icon svg{transition:transform .3s}
|
.ant-select-arrow .ant-select-arrow-icon svg{transition:transform .3s}
|
||||||
.ant-select-selection{display:block;box-sizing:border-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-radius:4px;outline:0;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
.ant-select-selection{display:block;box-sizing:border-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-radius:4px;outline:0;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
.ant-select-selection:hover{border-color:#40a9ff;border-right-width:1px!important}
|
.ant-select-selection:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
||||||
.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:rgb(0, 150, 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||||
.ant-select-selection__clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;font-style:normal;line-height:12px;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}
|
.ant-select-selection__clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;font-style:normal;line-height:12px;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}
|
||||||
.ant-select-selection__clear:before{display:block}
|
.ant-select-selection__clear:before{display:block}
|
||||||
.ant-select-selection__clear:hover{color:rgba(0,0,0,.45)}
|
.ant-select-selection__clear:hover{color:rgba(0,0,0,.45)}
|
||||||
@@ -529,7 +529,7 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
|||||||
.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
||||||
.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}
|
.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}
|
||||||
.ant-select-disabled{color:rgba(0,0,0,.25)}
|
.ant-select-disabled{color:rgba(0,0,0,.25)}
|
||||||
.ant-select-disabled .ant-select-selection{background:#f5f5f5;cursor:not-allowed}
|
.ant-select-disabled .ant-select-selection{background:rgb(221, 221, 221);cursor:not-allowed}
|
||||||
.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}
|
.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}
|
||||||
.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}
|
.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}
|
||||||
.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}
|
.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}
|
||||||
@@ -582,7 +582,7 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
|||||||
.ant-select-selection--multiple .ant-select-arrow,.ant-select-selection--multiple .ant-select-selection__clear{top:16px}
|
.ant-select-selection--multiple .ant-select-arrow,.ant-select-selection--multiple .ant-select-selection__clear{top:16px}
|
||||||
.ant-select-allow-clear .ant-select-selection--multiple .ant-select-selection__rendered,.ant-select-show-arrow .ant-select-selection--multiple .ant-select-selection__rendered{margin-right:20px}
|
.ant-select-allow-clear .ant-select-selection--multiple .ant-select-selection__rendered,.ant-select-show-arrow .ant-select-selection--multiple .ant-select-selection__rendered{margin-right:20px}
|
||||||
.ant-select-open .ant-select-arrow-icon svg{transform:rotate(180deg)}
|
.ant-select-open .ant-select-arrow-icon svg{transform:rotate(180deg)}
|
||||||
.ant-select-open .ant-select-selection{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-select-open .ant-select-selection{border-color:rgb(0 150 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||||
.ant-select-combobox .ant-select-arrow{display:none}
|
.ant-select-combobox .ant-select-arrow{display:none}
|
||||||
.ant-select-combobox .ant-select-search--inline{float:none;width:100%;height:100%}
|
.ant-select-combobox .ant-select-search--inline{float:none;width:100%;height:100%}
|
||||||
.ant-select-combobox .ant-select-search__field__wrap{width:100%;height:100%}
|
.ant-select-combobox .ant-select-search__field__wrap{width:100%;height:100%}
|
||||||
@@ -629,8 +629,8 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
|||||||
.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}
|
.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}
|
||||||
.ant-input:-ms-input-placeholder{text-overflow:ellipsis}
|
.ant-input:-ms-input-placeholder{text-overflow:ellipsis}
|
||||||
.ant-input:placeholder-shown{text-overflow:ellipsis}
|
.ant-input:placeholder-shown{text-overflow:ellipsis}
|
||||||
.ant-input:focus,.ant-input:hover{border-color:#40a9ff;border-right-width:1px!important}
|
.ant-input:focus,.ant-input:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important;}
|
||||||
.ant-input:focus{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-input:focus{outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||||
.ant-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
.ant-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||||
.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||||
.ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
.ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||||
@@ -716,7 +716,7 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
|||||||
.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
||||||
.ant-btn>a:only-child{color:currentColor}
|
.ant-btn>a:only-child{color:currentColor}
|
||||||
.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn:focus,.ant-btn:hover{color:#40a9ff;background-color:#fff;border-color:#40a9ff}
|
.ant-btn:focus,.ant-btn:hover{color:rgb(0 150 112);background-color:#fff;border-color:rgb(0 150 112)}
|
||||||
.ant-btn:focus>a:only-child,.ant-btn:hover>a:only-child{color:currentColor}
|
.ant-btn:focus>a:only-child,.ant-btn:hover>a:only-child{color:currentColor}
|
||||||
.ant-btn:focus>a:only-child:after,.ant-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn:focus>a:only-child:after,.ant-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn.active,.ant-btn:active{color:#096dd9;background-color:#fff;border-color:#096dd9}
|
.ant-btn.active,.ant-btn:active{color:#096dd9;background-color:#fff;border-color:#096dd9}
|
||||||
@@ -727,16 +727,16 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
|||||||
.ant-btn-disabled.active>a:only-child:after,.ant-btn-disabled:active>a:only-child:after,.ant-btn-disabled:focus>a:only-child:after,.ant-btn-disabled:hover>a:only-child:after,.ant-btn-disabled>a:only-child:after,.ant-btn.disabled.active>a:only-child:after,.ant-btn.disabled:active>a:only-child:after,.ant-btn.disabled:focus>a:only-child:after,.ant-btn.disabled:hover>a:only-child:after,.ant-btn.disabled>a:only-child:after,.ant-btn[disabled].active>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-disabled.active>a:only-child:after,.ant-btn-disabled:active>a:only-child:after,.ant-btn-disabled:focus>a:only-child:after,.ant-btn-disabled:hover>a:only-child:after,.ant-btn-disabled>a:only-child:after,.ant-btn.disabled.active>a:only-child:after,.ant-btn.disabled:active>a:only-child:after,.ant-btn.disabled:focus>a:only-child:after,.ant-btn.disabled:hover>a:only-child:after,.ant-btn.disabled>a:only-child:after,.ant-btn[disabled].active>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn.active,.ant-btn:active,.ant-btn:focus,.ant-btn:hover{text-decoration:none;background:#fff}
|
.ant-btn.active,.ant-btn:active,.ant-btn:focus,.ant-btn:hover{text-decoration:none;background:#fff}
|
||||||
.ant-btn>i,.ant-btn>span{display:inline-block;transition:margin-left .3s cubic-bezier(.645,.045,.355,1);pointer-events:none}
|
.ant-btn>i,.ant-btn>span{display:inline-block;transition:margin-left .3s cubic-bezier(.645,.045,.355,1);pointer-events:none}
|
||||||
.ant-btn-primary{color:#fff;background-color:#1890ff;border-color:#1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
.ant-btn-primary{color:#fff;background-color:rgb(0, 150, 112);border-color:rgb(0, 150, 112);text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||||
.ant-btn-primary>a:only-child{color:currentColor}
|
.ant-btn-primary>a:only-child{color:currentColor}
|
||||||
.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:#40a9ff;border-color:#00000017}
|
.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:rgb(0, 185, 138);border-color:#00000017}
|
||||||
.ant-btn-primary:focus>a:only-child,.ant-btn-primary:hover>a:only-child{color:currentColor}
|
.ant-btn-primary:focus>a:only-child,.ant-btn-primary:hover>a:only-child{color:currentColor}
|
||||||
.ant-btn-primary:focus>a:only-child:after,.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-primary:focus>a:only-child:after,.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:#096dd9;border-color:#096dd9}
|
.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:rgb(25, 191, 149);border-color:rgb(25, 191, 149)}
|
||||||
.ant-btn-primary.active>a:only-child,.ant-btn-primary:active>a:only-child{color:currentColor}
|
.ant-btn-primary.active>a:only-child,.ant-btn-primary:active>a:only-child{color:currentColor}
|
||||||
.ant-btn-primary.active>a:only-child:after,.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-primary.active>a:only-child:after,.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||||
.ant-btn-primary-disabled.active>a:only-child,.ant-btn-primary-disabled:active>a:only-child,.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-primary-disabled>a:only-child,.ant-btn-primary.disabled.active>a:only-child,.ant-btn-primary.disabled:active>a:only-child,.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-primary.disabled>a:only-child,.ant-btn-primary[disabled].active>a:only-child,.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]>a:only-child{color:currentColor}
|
.ant-btn-primary-disabled.active>a:only-child,.ant-btn-primary-disabled:active>a:only-child,.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-primary-disabled>a:only-child,.ant-btn-primary.disabled.active>a:only-child,.ant-btn-primary.disabled:active>a:only-child,.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-primary.disabled>a:only-child,.ant-btn-primary[disabled].active>a:only-child,.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]>a:only-child{color:currentColor}
|
||||||
.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-primary-disabled>a:only-child:after,.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-primary.disabled>a:only-child:after,.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-primary-disabled>a:only-child:after,.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-primary.disabled>a:only-child:after,.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#40a9ff;border-left-color:#40a9ff}
|
.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#40a9ff;border-left-color:#40a9ff}
|
||||||
@@ -769,16 +769,16 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
|||||||
.ant-btn-dashed-disabled,.ant-btn-dashed-disabled.active,.ant-btn-dashed-disabled:active,.ant-btn-dashed-disabled:focus,.ant-btn-dashed-disabled:hover,.ant-btn-dashed.disabled,.ant-btn-dashed.disabled.active,.ant-btn-dashed.disabled:active,.ant-btn-dashed.disabled:focus,.ant-btn-dashed.disabled:hover,.ant-btn-dashed[disabled],.ant-btn-dashed[disabled].active,.ant-btn-dashed[disabled]:active,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
.ant-btn-dashed-disabled,.ant-btn-dashed-disabled.active,.ant-btn-dashed-disabled:active,.ant-btn-dashed-disabled:focus,.ant-btn-dashed-disabled:hover,.ant-btn-dashed.disabled,.ant-btn-dashed.disabled.active,.ant-btn-dashed.disabled:active,.ant-btn-dashed.disabled:focus,.ant-btn-dashed.disabled:hover,.ant-btn-dashed[disabled],.ant-btn-dashed[disabled].active,.ant-btn-dashed[disabled]:active,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
||||||
.ant-btn-dashed-disabled.active>a:only-child,.ant-btn-dashed-disabled:active>a:only-child,.ant-btn-dashed-disabled:focus>a:only-child,.ant-btn-dashed-disabled:hover>a:only-child,.ant-btn-dashed-disabled>a:only-child,.ant-btn-dashed.disabled.active>a:only-child,.ant-btn-dashed.disabled:active>a:only-child,.ant-btn-dashed.disabled:focus>a:only-child,.ant-btn-dashed.disabled:hover>a:only-child,.ant-btn-dashed.disabled>a:only-child,.ant-btn-dashed[disabled].active>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]>a:only-child{color:currentColor}
|
.ant-btn-dashed-disabled.active>a:only-child,.ant-btn-dashed-disabled:active>a:only-child,.ant-btn-dashed-disabled:focus>a:only-child,.ant-btn-dashed-disabled:hover>a:only-child,.ant-btn-dashed-disabled>a:only-child,.ant-btn-dashed.disabled.active>a:only-child,.ant-btn-dashed.disabled:active>a:only-child,.ant-btn-dashed.disabled:focus>a:only-child,.ant-btn-dashed.disabled:hover>a:only-child,.ant-btn-dashed.disabled>a:only-child,.ant-btn-dashed[disabled].active>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]>a:only-child{color:currentColor}
|
||||||
.ant-btn-dashed-disabled.active>a:only-child:after,.ant-btn-dashed-disabled:active>a:only-child:after,.ant-btn-dashed-disabled:focus>a:only-child:after,.ant-btn-dashed-disabled:hover>a:only-child:after,.ant-btn-dashed-disabled>a:only-child:after,.ant-btn-dashed.disabled.active>a:only-child:after,.ant-btn-dashed.disabled:active>a:only-child:after,.ant-btn-dashed.disabled:focus>a:only-child:after,.ant-btn-dashed.disabled:hover>a:only-child:after,.ant-btn-dashed.disabled>a:only-child:after,.ant-btn-dashed[disabled].active>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-dashed-disabled.active>a:only-child:after,.ant-btn-dashed-disabled:active>a:only-child:after,.ant-btn-dashed-disabled:focus>a:only-child:after,.ant-btn-dashed-disabled:hover>a:only-child:after,.ant-btn-dashed-disabled>a:only-child:after,.ant-btn-dashed.disabled.active>a:only-child:after,.ant-btn-dashed.disabled:active>a:only-child:after,.ant-btn-dashed.disabled:focus>a:only-child:after,.ant-btn-dashed.disabled:hover>a:only-child:after,.ant-btn-dashed.disabled>a:only-child:after,.ant-btn-dashed[disabled].active>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-danger{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
.ant-btn-danger{color:#ff4d4f;background-color:rgb(255 77 79 / 0%);border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||||
.ant-btn-danger>a:only-child{color:currentColor}
|
.ant-btn-danger>a:only-child{color:currentColor}
|
||||||
.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff7875;border-color:#ff7875}
|
.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f}
|
||||||
.ant-btn-danger:focus>a:only-child,.ant-btn-danger:hover>a:only-child{color:currentColor}
|
.ant-btn-danger:focus>a:only-child,.ant-btn-danger:hover>a:only-child{color:currentColor}
|
||||||
.ant-btn-danger:focus>a:only-child:after,.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-danger:focus>a:only-child:after,.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-danger.active,.ant-btn-danger:active{color:#fff;background-color:#d9363e;border-color:#d9363e}
|
.ant-btn-danger.active,.ant-btn-danger:active{color:#fff;background-color:#d9363e;border-color:#d9363e}
|
||||||
.ant-btn-danger.active>a:only-child,.ant-btn-danger:active>a:only-child{color:currentColor}
|
.ant-btn-danger.active>a:only-child,.ant-btn-danger:active>a:only-child{color:currentColor}
|
||||||
.ant-btn-danger.active>a:only-child:after,.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-danger.active>a:only-child:after,.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||||
.ant-btn-danger-disabled.active>a:only-child,.ant-btn-danger-disabled:active>a:only-child,.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-danger-disabled>a:only-child,.ant-btn-danger.disabled.active>a:only-child,.ant-btn-danger.disabled:active>a:only-child,.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-danger.disabled>a:only-child,.ant-btn-danger[disabled].active>a:only-child,.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]>a:only-child{color:currentColor}
|
.ant-btn-danger-disabled.active>a:only-child,.ant-btn-danger-disabled:active>a:only-child,.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-danger-disabled>a:only-child,.ant-btn-danger.disabled.active>a:only-child,.ant-btn-danger.disabled:active>a:only-child,.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-danger.disabled>a:only-child,.ant-btn-danger[disabled].active>a:only-child,.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]>a:only-child{color:currentColor}
|
||||||
.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-danger-disabled>a:only-child:after,.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-danger.disabled>a:only-child:after,.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-danger-disabled>a:only-child:after,.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-danger.disabled>a:only-child:after,.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-btn-link{color:#1890ff;background-color:transparent;border-color:transparent;box-shadow:none}
|
.ant-btn-link{color:#1890ff;background-color:transparent;border-color:transparent;box-shadow:none}
|
||||||
@@ -992,11 +992,11 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}
|
.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}
|
||||||
.ant-menu-item>.ant-badge>a:hover{color:#1890ff}
|
.ant-menu-item>.ant-badge>a:hover{color:#1890ff}
|
||||||
.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}
|
.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}
|
||||||
.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#fff;background-image:linear-gradient(-20deg,#1a61b3 0,#242d81 100%)}
|
.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#2d2d2d;background-image: linear-gradient(90deg,#99999980 0,#8888889e 100%);border-radius: 0.5rem}
|
||||||
.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}
|
.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}
|
||||||
.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}
|
.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}
|
||||||
.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff}
|
.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff}
|
||||||
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background:linear-gradient(-20deg,#412ef0 0,#0a2d58 100%);color:#fff}
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background: linear-gradient(90deg,#009670 0,#026247 100%);color: #fff;border-radius: 0.5rem}
|
||||||
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
||||||
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
||||||
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
||||||
@@ -1032,7 +1032,7 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-horizontal>.ant-menu-item-selected>a{color:#1890ff}
|
.ant-menu-horizontal>.ant-menu-item-selected>a{color:#1890ff}
|
||||||
.ant-menu-horizontal:after{display:block;clear:both;height:0;content:"\20"}
|
.ant-menu-horizontal:after{display:block;clear:both;height:0;content:"\20"}
|
||||||
.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item{position:relative}
|
.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item{position:relative}
|
||||||
.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;/*! border-right:3px solid #1890ff; */transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}
|
.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0/*;border-right:3px solid #1890ff*/;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}
|
||||||
.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}
|
.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}
|
||||||
.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu{padding-bottom:.02px}
|
.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu{padding-bottom:.02px}
|
||||||
.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child){margin-bottom:8px}
|
.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child){margin-bottom:8px}
|
||||||
@@ -1080,8 +1080,9 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-dark .ant-menu-item:hover{background-color:transparent}
|
.ant-menu-dark .ant-menu-item:hover{background-color:transparent}
|
||||||
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#fff}
|
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#ffffff}
|
||||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#1890ff}
|
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#15223a}
|
||||||
|
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#38383800}
|
||||||
.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}
|
.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}
|
||||||
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}
|
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}
|
||||||
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}
|
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}
|
||||||
@@ -1235,18 +1236,18 @@ span.ant-radio+*{padding-right:8px;padding-left:8px}
|
|||||||
.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:4px 0 0 4px}
|
.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:4px 0 0 4px}
|
||||||
.ant-radio-button-wrapper:last-child{border-radius:0 4px 4px 0}
|
.ant-radio-button-wrapper:last-child{border-radius:0 4px 4px 0}
|
||||||
.ant-radio-button-wrapper:first-child:last-child{border-radius:4px}
|
.ant-radio-button-wrapper:first-child:last-child{border-radius:4px}
|
||||||
.ant-radio-button-wrapper:hover{position:relative;color:#1890ff}
|
.ant-radio-button-wrapper:hover{position:relative;color:#009670}
|
||||||
.ant-radio-button-wrapper:focus-within{outline:3px solid rgba(24,144,255,.06)}
|
.ant-radio-button-wrapper:focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||||
.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}
|
.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#1890ff;background:#fff;border-color:#1890ff;box-shadow:-1px 0 0 0 #1890ff}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#009670;background:#fff;border-color:#009670;box-shadow:-1px 0 0 0 #009670}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#1890ff!important;opacity:.1}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#009670!important;opacity:.1}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#1890ff;box-shadow:none!important}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#009670;box-shadow:none!important}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#40a9ff;border-color:#40a9ff;box-shadow:-1px 0 0 0 #40a9ff}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#009670;border-color:#009670;box-shadow:-1px 0 0 0 #009670}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#096dd9;border-color:#096dd9;box-shadow:-1px 0 0 0 #096dd9}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#076e54;border-color:#076e54;box-shadow:-1px 0 0 0 #076e54}
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#1890ff;border-color:#1890ff}
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#009670;border-color:#009670}
|
||||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#40a9ff;border-color:#40a9ff}
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#009670;border-color:#009670}
|
||||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#096dd9;border-color:#096dd9}
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#076e54;border-color:#076e54}
|
||||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||||
.ant-radio-button-wrapper-disabled{cursor:not-allowed}
|
.ant-radio-button-wrapper-disabled{cursor:not-allowed}
|
||||||
.ant-radio-button-wrapper-disabled,.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9}
|
.ant-radio-button-wrapper-disabled,.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9}
|
||||||
@@ -1263,11 +1264,11 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
@supports (-moz-appearance:meterbar) and (background-blend-mode:difference,normal){
|
@supports (-moz-appearance:meterbar) and (background-blend-mode:difference,normal){
|
||||||
.ant-radio{vertical-align:text-bottom}
|
.ant-radio{vertical-align:text-bottom}
|
||||||
}
|
}
|
||||||
.ant-card{box-sizing:border-box;margin:auto 5px 3px auto;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;background:#fff;border-radius:2px;transition:all .3s}
|
.ant-card{box-sizing:border-box;margin:auto 10px 3px auto;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;background:#fff;border-radius:2px;transition:all .3s}
|
||||||
.ant-card-hoverable{cursor:pointer}
|
.ant-card-hoverable{cursor:pointer}
|
||||||
.ant-card-hoverable:hover{border-color:rgba(0,0,0,.09);box-shadow:0 2px 8px rgba(0,0,0,.09)}
|
.ant-card-hoverable:hover{border-color:rgba(0,0,0,.09);box-shadow:0 2px 8px rgba(0,0,0,.09)}
|
||||||
.ant-card-bordered{/*! border:1px solid #e8e8e8; *//*! box-shadow: 0px 6px 0px 0px rgb(15, 197, 254); *//*! box-shadow: 0 4px 5px 0 rgba(170, 179, 217, 0.33); */backdrop-filter:blur(7px) saturate(200%);background-color:#fff;border:0 solid rgba(255,255,255,0);box-shadow:0 3px 7.985px -.985px #9aafee}
|
.ant-card-bordered{/*! border:1px solid #e8e8e8; *//*! box-shadow: 0px 6px 0px 0px rgb(15, 197, 254); *//*! box-shadow: 0 4px 5px 0 rgba(170, 179, 217, 0.33); */backdrop-filter:blur(7px) saturate(200%);background-color:#fff;border:0 solid rgba(255,255,255,0)/*;box-shadow:0 1px 7px -1px #0000005c*/}
|
||||||
.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;background:0 0;border-bottom:1px solid #e8e8e8;border-radius:2px 2px 0 0;zoom:1}
|
.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;background:0 0;border-bottom:2px solid rgba(155, 155, 155, 0.15);border-radius:2px 2px 0 0;zoom:1}
|
||||||
.ant-card-head:after,.ant-card-head:before{display:table;content:""}
|
.ant-card-head:after,.ant-card-head:before{display:table;content:""}
|
||||||
.ant-card-head:after{clear:both}
|
.ant-card-head:after{clear:both}
|
||||||
.ant-card-head-wrapper{display:flex;align-items:center}
|
.ant-card-head-wrapper{display:flex;align-items:center}
|
||||||
@@ -1356,11 +1357,12 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}
|
.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}
|
||||||
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
||||||
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#1890ff}
|
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#1890ff}
|
||||||
.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1}
|
.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1;border-radius:1.5rem;/*box-shadow:0 1px 7px -1px #0000005c;*/transition:all .3s;background-color: white}
|
||||||
|
.ant-tabs:hover{box-shadow:0 3px 12px -.8px #0000005c}
|
||||||
.ant-tabs:after,.ant-tabs:before{display:table;content:""}
|
.ant-tabs:after,.ant-tabs:before{display:table;content:""}
|
||||||
.ant-tabs:after{clear:both}
|
.ant-tabs:after{clear:both}
|
||||||
.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;box-sizing:border-box;width:0;height:2px;background-color:#1890ff;transform-origin:0 0}
|
.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;box-sizing:border-box;width:0;height:2px;background-color:rgb(0 150 112);transform-origin:0 0}
|
||||||
.ant-tabs-bar{margin:0 0 16px;border-bottom:1px solid #e8e8e8;outline:0}
|
.ant-tabs-bar{margin:1.5rem 1.5rem 0rem 1.5rem!important;border-bottom:2px solid rgb(153 153 153 / 20%);outline:0}
|
||||||
.ant-tabs-bar,.ant-tabs-nav-container{transition:padding .3s cubic-bezier(.645,.045,.355,1)}
|
.ant-tabs-bar,.ant-tabs-nav-container{transition:padding .3s cubic-bezier(.645,.045,.355,1)}
|
||||||
.ant-tabs-nav-container{position:relative;box-sizing:border-box;margin-bottom:-1px;overflow:hidden;font-size:14px;line-height:1.5;white-space:nowrap;zoom:1}
|
.ant-tabs-nav-container{position:relative;box-sizing:border-box;margin-bottom:-1px;overflow:hidden;font-size:14px;line-height:1.5;white-space:nowrap;zoom:1}
|
||||||
.ant-tabs-nav-container:after,.ant-tabs-nav-container:before{display:table;content:""}
|
.ant-tabs-nav-container:after,.ant-tabs-nav-container:before{display:table;content:""}
|
||||||
@@ -1388,10 +1390,10 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-tabs-nav .ant-tabs-tab{position:relative;display:inline-block;box-sizing:border-box;height:100%;margin:0 32px 0 0;padding:12px 16px;text-decoration:none;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1)}
|
.ant-tabs-nav .ant-tabs-tab{position:relative;display:inline-block;box-sizing:border-box;height:100%;margin:0 32px 0 0;padding:12px 16px;text-decoration:none;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1)}
|
||||||
.ant-tabs-nav .ant-tabs-tab:before{position:absolute;top:-1px;left:0;width:100%;border-top:2px solid transparent;border-radius:4px 4px 0 0;transition:all .3s;content:"";pointer-events:none}
|
.ant-tabs-nav .ant-tabs-tab:before{position:absolute;top:-1px;left:0;width:100%;border-top:2px solid transparent;border-radius:4px 4px 0 0;transition:all .3s;content:"";pointer-events:none}
|
||||||
.ant-tabs-nav .ant-tabs-tab:last-child{margin-right:0}
|
.ant-tabs-nav .ant-tabs-tab:last-child{margin-right:0}
|
||||||
.ant-tabs-nav .ant-tabs-tab:hover{color:#40a9ff}
|
.ant-tabs-nav .ant-tabs-tab:hover{color:rgb(0 150 112)}
|
||||||
.ant-tabs-nav .ant-tabs-tab:active{color:#096dd9}
|
.ant-tabs-nav .ant-tabs-tab:active{color:rgb(0 150 112)}
|
||||||
.ant-tabs-nav .ant-tabs-tab .anticon{margin-right:8px}
|
.ant-tabs-nav .ant-tabs-tab .anticon{margin-right:8px}
|
||||||
.ant-tabs-nav .ant-tabs-tab-active{color:#1890ff;font-weight:500}
|
.ant-tabs-nav .ant-tabs-tab-active{color:rgb(0 150 112);font-weight:500}
|
||||||
.ant-tabs-nav .ant-tabs-tab-disabled,.ant-tabs-nav .ant-tabs-tab-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}
|
.ant-tabs-nav .ant-tabs-tab-disabled,.ant-tabs-nav .ant-tabs-tab-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}
|
||||||
.ant-tabs .ant-tabs-large-bar .ant-tabs-nav-container{font-size:16px}
|
.ant-tabs .ant-tabs-large-bar .ant-tabs-nav-container{font-size:16px}
|
||||||
.ant-tabs .ant-tabs-large-bar .ant-tabs-tab{padding:16px}
|
.ant-tabs .ant-tabs-large-bar .ant-tabs-tab{padding:16px}
|
||||||
@@ -2436,14 +2438,14 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:rgba(0,0,0,.25)}
|
.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:rgba(0,0,0,.25)}
|
||||||
.ant-cascader-menu-item .ant-cascader-menu-item-keyword{color:#f5222d}
|
.ant-cascader-menu-item .ant-cascader-menu-item-keyword{color:#f5222d}
|
||||||
.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:0;cursor:pointer}
|
.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:0;cursor:pointer}
|
||||||
.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:#1890ff}
|
.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:rgb(0, 150, 112)}
|
||||||
.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:""}
|
.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid rgb(0, 150, 112);border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:""}
|
||||||
.ant-checkbox-wrapper:hover .ant-checkbox:after,.ant-checkbox:hover:after{visibility:visible}
|
.ant-checkbox-wrapper:hover .ant-checkbox:after,.ant-checkbox:hover:after{visibility:visible}
|
||||||
.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}
|
.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}
|
||||||
.ant-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}
|
.ant-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}
|
||||||
.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}
|
.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}
|
||||||
.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}
|
.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}
|
||||||
.ant-checkbox-checked .ant-checkbox-inner{background-color:#1890ff;border-color:#1890ff}
|
.ant-checkbox-checked .ant-checkbox-inner{background-color:rgb(0, 150, 112);border-color:rgb(0, 150, 112)}
|
||||||
.ant-checkbox-disabled{cursor:not-allowed}
|
.ant-checkbox-disabled{cursor:not-allowed}
|
||||||
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}
|
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}
|
||||||
.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed}
|
.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed}
|
||||||
@@ -2462,9 +2464,9 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}
|
.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}
|
||||||
.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}
|
.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}
|
||||||
.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}
|
.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}
|
||||||
.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:4px}
|
.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"/*;background-color:#fafafa*/;border:1px solid rgba(100, 100, 100, 0.2);/*border-bottom:0;*/border-radius:0.5rem}
|
||||||
.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}
|
.ant-collapse>.ant-collapse-item{border-bottom:1px solid rgb(217 217 217 / 20%)}
|
||||||
.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 4px 4px}
|
.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0.5rem}
|
||||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 16px 12px 40px;color:rgba(0,0,0,.85);line-height:22px;cursor:pointer;transition:all .3s}
|
.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 16px 12px 40px;color:rgba(0,0,0,.85);line-height:22px;cursor:pointer;transition:all .3s}
|
||||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;left:16px;display:inline-block;font-size:12px;transform:translateY(-50%)}
|
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;left:16px;display:inline-block;font-size:12px;transform:translateY(-50%)}
|
||||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow>*{line-height:1}
|
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow>*{line-height:1}
|
||||||
@@ -2478,7 +2480,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}
|
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}
|
||||||
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{right:16px;left:auto}
|
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{right:16px;left:auto}
|
||||||
.ant-collapse-anim-active{transition:height .2s cubic-bezier(.215,.61,.355,1)}
|
.ant-collapse-anim-active{transition:height .2s cubic-bezier(.215,.61,.355,1)}
|
||||||
.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid #d9d9d9}
|
.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid rgb(0 150 112)}
|
||||||
.ant-collapse-content>.ant-collapse-content-box{padding:16px}
|
.ant-collapse-content>.ant-collapse-content-box{padding:16px}
|
||||||
.ant-collapse-content-inactive{display:none}
|
.ant-collapse-content-inactive{display:none}
|
||||||
.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 4px 4px}
|
.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 4px 4px}
|
||||||
@@ -2537,7 +2539,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar-picker-input{outline:0}
|
.ant-calendar-picker-input{outline:0}
|
||||||
.ant-calendar-picker-input.ant-input{line-height:1.5}
|
.ant-calendar-picker-input.ant-input{line-height:1.5}
|
||||||
.ant-calendar-picker-input.ant-input-sm{padding-top:0;padding-bottom:0}
|
.ant-calendar-picker-input.ant-input-sm{padding-top:0;padding-bottom:0}
|
||||||
.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff}
|
.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:rgb(0 150 112)}
|
||||||
.ant-calendar-picker:focus .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-calendar-picker:focus .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||||
.ant-calendar-picker-clear,.ant-calendar-picker-icon{position:absolute;top:50%;right:12px;z-index:1;width:14px;height:14px;margin-top:-7px;font-size:12px;line-height:14px;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
.ant-calendar-picker-clear,.ant-calendar-picker-icon{position:absolute;top:50%;right:12px;z-index:1;width:14px;height:14px;margin-top:-7px;font-size:12px;line-height:14px;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
.ant-calendar-picker-clear{z-index:2;color:rgba(0,0,0,.25);font-size:14px;background:#fff;cursor:pointer;opacity:0;pointer-events:none}
|
.ant-calendar-picker-clear{z-index:2;color:rgba(0,0,0,.25);font-size:14px;background:#fff;cursor:pointer;opacity:0;pointer-events:none}
|
||||||
@@ -2547,7 +2549,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-input-disabled+.ant-calendar-picker-icon{cursor:not-allowed}
|
.ant-input-disabled+.ant-calendar-picker-icon{cursor:not-allowed}
|
||||||
.ant-calendar-picker-small .ant-calendar-picker-clear,.ant-calendar-picker-small .ant-calendar-picker-icon{right:8px}
|
.ant-calendar-picker-small .ant-calendar-picker-clear,.ant-calendar-picker-small .ant-calendar-picker-icon{right:8px}
|
||||||
.ant-calendar{position:relative;width:280px;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #fff;border-radius:4px;outline:0;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
.ant-calendar{position:relative;width:280px;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #fff;border-radius:4px;outline:0;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||||||
.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid #e8e8e8}
|
.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid rgb(153 153 153 / 25%)}
|
||||||
.ant-calendar-input{width:100%;height:22px;color:rgba(0,0,0,.65);background:#fff;border:0;outline:0;cursor:auto}
|
.ant-calendar-input{width:100%;height:22px;color:rgba(0,0,0,.65);background:#fff;border:0;outline:0;cursor:auto}
|
||||||
.ant-calendar-input::-moz-placeholder{color:#bfbfbf;opacity:1}
|
.ant-calendar-input::-moz-placeholder{color:#bfbfbf;opacity:1}
|
||||||
.ant-calendar-input:-ms-input-placeholder{color:#bfbfbf}
|
.ant-calendar-input:-ms-input-placeholder{color:#bfbfbf}
|
||||||
@@ -2557,7 +2559,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar-input:placeholder-shown{text-overflow:ellipsis}
|
.ant-calendar-input:placeholder-shown{text-overflow:ellipsis}
|
||||||
.ant-calendar-week-number{width:286px}
|
.ant-calendar-week-number{width:286px}
|
||||||
.ant-calendar-week-number-cell{text-align:center}
|
.ant-calendar-week-number-cell{text-align:center}
|
||||||
.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid rgb(153 153 153 / 25%);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
.ant-calendar-header a:hover{color:#40a9ff}
|
.ant-calendar-header a:hover{color:#40a9ff}
|
||||||
.ant-calendar-header .ant-calendar-century-select,.ant-calendar-header .ant-calendar-decade-select,.ant-calendar-header .ant-calendar-month-select,.ant-calendar-header .ant-calendar-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}
|
.ant-calendar-header .ant-calendar-century-select,.ant-calendar-header .ant-calendar-decade-select,.ant-calendar-header .ant-calendar-month-select,.ant-calendar-header .ant-calendar-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}
|
||||||
.ant-calendar-header .ant-calendar-century-select-arrow,.ant-calendar-header .ant-calendar-decade-select-arrow,.ant-calendar-header .ant-calendar-month-select-arrow,.ant-calendar-header .ant-calendar-year-select-arrow{display:none}
|
.ant-calendar-header .ant-calendar-century-select-arrow,.ant-calendar-header .ant-calendar-decade-select-arrow,.ant-calendar-header .ant-calendar-month-select-arrow,.ant-calendar-header .ant-calendar-year-select-arrow{display:none}
|
||||||
@@ -2593,8 +2595,8 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar-date{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:22px;text-align:center;background:0 0;border:1px solid transparent;border-radius:2px;transition:background .3s ease}
|
.ant-calendar-date{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:22px;text-align:center;background:0 0;border:1px solid transparent;border-radius:2px;transition:background .3s ease}
|
||||||
.ant-calendar-date-panel{position:relative;outline:0}
|
.ant-calendar-date-panel{position:relative;outline:0}
|
||||||
.ant-calendar-date:hover{background:#e6f7ff;cursor:pointer}
|
.ant-calendar-date:hover{background:#e6f7ff;cursor:pointer}
|
||||||
.ant-calendar-date:active{color:#fff;background:#40a9ff}
|
.ant-calendar-date:active{color:rgba(0,0,0,.65);background:#bae7ff}
|
||||||
.ant-calendar-today .ant-calendar-date{color:#1890ff;font-weight:700;border-color:#1890ff}
|
.ant-calendar-today .ant-calendar-date{color:rgb(0 150 112);font-weight:700;border-color:rgb(0 150 112)}
|
||||||
.ant-calendar-selected-day .ant-calendar-date{background:#bae7ff}
|
.ant-calendar-selected-day .ant-calendar-date{background:#bae7ff}
|
||||||
.ant-calendar-last-month-cell .ant-calendar-date,.ant-calendar-last-month-cell .ant-calendar-date:hover,.ant-calendar-next-month-btn-day .ant-calendar-date,.ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgba(0,0,0,.25);background:0 0;border-color:transparent}
|
.ant-calendar-last-month-cell .ant-calendar-date,.ant-calendar-last-month-cell .ant-calendar-date:hover,.ant-calendar-next-month-btn-day .ant-calendar-date,.ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgba(0,0,0,.25);background:0 0;border-color:transparent}
|
||||||
.ant-calendar-disabled-cell .ant-calendar-date{position:relative;width:auto;color:rgba(0,0,0,.25);background:#f5f5f5;border:1px solid transparent;border-radius:0;cursor:not-allowed}
|
.ant-calendar-disabled-cell .ant-calendar-date{position:relative;width:auto;color:rgba(0,0,0,.25);background:#f5f5f5;border:1px solid transparent;border-radius:0;cursor:not-allowed}
|
||||||
@@ -2604,7 +2606,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;border:1px solid rgba(0,0,0,.25);border-radius:2px;content:" "}
|
.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;border:1px solid rgba(0,0,0,.25);border-radius:2px;content:" "}
|
||||||
.ant-calendar-disabled-cell-first-of-row .ant-calendar-date{border-top-left-radius:4px;border-bottom-left-radius:4px}
|
.ant-calendar-disabled-cell-first-of-row .ant-calendar-date{border-top-left-radius:4px;border-bottom-left-radius:4px}
|
||||||
.ant-calendar-disabled-cell-last-of-row .ant-calendar-date{border-top-right-radius:4px;border-bottom-right-radius:4px}
|
.ant-calendar-disabled-cell-last-of-row .ant-calendar-date{border-top-right-radius:4px;border-bottom-right-radius:4px}
|
||||||
.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid #e8e8e8}
|
.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid rgb(153 153 153 / 25%)}
|
||||||
.ant-calendar-footer:empty{border-top:0}
|
.ant-calendar-footer:empty{border-top:0}
|
||||||
.ant-calendar-footer-btn{display:block;text-align:center}
|
.ant-calendar-footer-btn{display:block;text-align:center}
|
||||||
.ant-calendar-footer-extra{text-align:left}
|
.ant-calendar-footer-extra{text-align:left}
|
||||||
@@ -2614,7 +2616,7 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar .ant-calendar-clear-btn{position:absolute;top:7px;right:5px;display:none;width:20px;height:20px;margin:0;overflow:hidden;line-height:20px;text-align:center;text-indent:-76px}
|
.ant-calendar .ant-calendar-clear-btn{position:absolute;top:7px;right:5px;display:none;width:20px;height:20px;margin:0;overflow:hidden;line-height:20px;text-align:center;text-indent:-76px}
|
||||||
.ant-calendar .ant-calendar-clear-btn:after{display:inline-block;width:20px;color:rgba(0,0,0,.25);font-size:14px;line-height:1;text-indent:43px;transition:color .3s ease}
|
.ant-calendar .ant-calendar-clear-btn:after{display:inline-block;width:20px;color:rgba(0,0,0,.25);font-size:14px;line-height:1;text-indent:43px;transition:color .3s ease}
|
||||||
.ant-calendar .ant-calendar-clear-btn:hover:after{color:rgba(0,0,0,.45)}
|
.ant-calendar .ant-calendar-clear-btn:hover:after{color:rgba(0,0,0,.45)}
|
||||||
.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;touch-action:manipulation;height:32px;color:#fff;background-color:#1890ff;border:1px solid #1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:4px;line-height:22px}
|
.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;touch-action:manipulation;height:32px;color:#fff;background-color:rgb(0 150 112);border:1px solid rgb(0 150 112);text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:4px;line-height:22px}
|
||||||
.ant-calendar .ant-calendar-ok-btn>.anticon{line-height:1}
|
.ant-calendar .ant-calendar-ok-btn>.anticon{line-height:1}
|
||||||
.ant-calendar .ant-calendar-ok-btn,.ant-calendar .ant-calendar-ok-btn:active,.ant-calendar .ant-calendar-ok-btn:focus{outline:0}
|
.ant-calendar .ant-calendar-ok-btn,.ant-calendar .ant-calendar-ok-btn:active,.ant-calendar .ant-calendar-ok-btn:focus{outline:0}
|
||||||
.ant-calendar .ant-calendar-ok-btn:not([disabled]):hover{text-decoration:none}
|
.ant-calendar .ant-calendar-ok-btn:not([disabled]):hover{text-decoration:none}
|
||||||
@@ -2625,13 +2627,13 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-calendar .ant-calendar-ok-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
.ant-calendar .ant-calendar-ok-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
||||||
.ant-calendar .ant-calendar-ok-btn>a:only-child{color:currentColor}
|
.ant-calendar .ant-calendar-ok-btn>a:only-child{color:currentColor}
|
||||||
.ant-calendar .ant-calendar-ok-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-calendar .ant-calendar-ok-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:#40a9ff;border-color:#40a9ff}
|
.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:rgb(0 150 112);border-color:rgb(0 150 112)}
|
||||||
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child{color:currentColor}
|
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child{color:currentColor}
|
||||||
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-calendar .ant-calendar-ok-btn.active,.ant-calendar .ant-calendar-ok-btn:active{color:#fff;background-color:#096dd9;border-color:#096dd9}
|
.ant-calendar .ant-calendar-ok-btn.active,.ant-calendar .ant-calendar-ok-btn:active{color:#fff;background-color:#096dd9;border-color:#096dd9}
|
||||||
.ant-calendar .ant-calendar-ok-btn.active>a:only-child,.ant-calendar .ant-calendar-ok-btn:active>a:only-child{color:currentColor}
|
.ant-calendar .ant-calendar-ok-btn.active>a:only-child,.ant-calendar .ant-calendar-ok-btn:active>a:only-child{color:currentColor}
|
||||||
.ant-calendar .ant-calendar-ok-btn.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-calendar .ant-calendar-ok-btn.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||||
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child{color:currentColor}
|
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child{color:currentColor}
|
||||||
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||||
.ant-calendar-range-picker-input{width:44%;height:99%;text-align:center;background-color:transparent;border:0;outline:0}
|
.ant-calendar-range-picker-input{width:44%;height:99%;text-align:center;background-color:transparent;border:0;outline:0}
|
||||||
@@ -2942,7 +2944,7 @@ textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-h
|
|||||||
.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}
|
.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}
|
||||||
.ant-tag-lime{color:#a0d911;background:#fcffe6;border-color:#eaff8f}
|
.ant-tag-lime{color:#a0d911;background:#fcffe6;border-color:#eaff8f}
|
||||||
.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}
|
.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}
|
||||||
.ant-tag-green{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}
|
.ant-tag-green{color:#19bf95;background:#ebfffa;border-color:#8ce7d0}
|
||||||
.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}
|
.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}
|
||||||
.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}
|
.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}
|
||||||
.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}
|
.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}
|
||||||
@@ -2977,8 +2979,8 @@ textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-h
|
|||||||
.ant-divider{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background:#e8e8e8}
|
.ant-divider{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background:#e8e8e8}
|
||||||
.ant-divider,.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;width:1px;height:.9em;margin:0 8px;vertical-align:middle}
|
.ant-divider,.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;width:1px;height:.9em;margin:0 8px;vertical-align:middle}
|
||||||
.ant-divider-horizontal{display:block;clear:both;width:100%;min-width:100%;height:1px;margin:24px 0}
|
.ant-divider-horizontal{display:block;clear:both;width:100%;min-width:100%;height:1px;margin:24px 0}
|
||||||
.ant-divider-horizontal.ant-divider-with-text-center,.ant-divider-horizontal.ant-divider-with-text-left,.ant-divider-horizontal.ant-divider-with-text-right{display:table;margin:16px 0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-align:center;background:0 0}
|
.ant-divider-horizontal.ant-divider-with-text-center,.ant-divider-horizontal.ant-divider-with-text-left,.ant-divider-horizontal.ant-divider-with-text-right{display:table;margin:0 0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-align:center;background:0 0}
|
||||||
.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid #e8e8e8;transform:translateY(50%);content:""}
|
.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid rgb(0 150 112 / 50%);transform:translateY(50%);content:""}
|
||||||
.ant-divider-horizontal.ant-divider-with-text-left .ant-divider-inner-text,.ant-divider-horizontal.ant-divider-with-text-right .ant-divider-inner-text{display:inline-block;padding:0 10px}
|
.ant-divider-horizontal.ant-divider-with-text-left .ant-divider-inner-text,.ant-divider-horizontal.ant-divider-with-text-right .ant-divider-inner-text{display:inline-block;padding:0 10px}
|
||||||
.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}
|
.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}
|
||||||
.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}
|
.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}
|
||||||
@@ -3214,7 +3216,7 @@ to{transform:scale(1)}
|
|||||||
.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}
|
.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}
|
||||||
.ant-input-number:-ms-input-placeholder{text-overflow:ellipsis}
|
.ant-input-number:-ms-input-placeholder{text-overflow:ellipsis}
|
||||||
.ant-input-number:placeholder-shown{text-overflow:ellipsis}
|
.ant-input-number:placeholder-shown{text-overflow:ellipsis}
|
||||||
.ant-input-number:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-input-number:focus{border-color:rgb(0, 150, 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px !important}
|
||||||
.ant-input-number[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
.ant-input-number[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||||
.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||||
textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}
|
textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}
|
||||||
@@ -3228,7 +3230,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}
|
.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}
|
||||||
.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}
|
.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}
|
||||||
.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}
|
.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}
|
||||||
.ant-input-number-focused,.ant-input-number:hover{border-color:#40a9ff;border-right-width:1px!important}
|
.ant-input-number-focused,.ant-input-number:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
||||||
.ant-input-number-focused{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-input-number-focused{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||||
.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||||
.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||||
@@ -3268,7 +3270,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-layout-footer{padding:24px 50px;color:rgba(0,0,0,.65);font-size:14px;background:#f0f2f5}
|
.ant-layout-footer{padding:24px 50px;color:rgba(0,0,0,.65);font-size:14px;background:#f0f2f5}
|
||||||
.ant-layout-content{flex:auto;min-height:0}
|
.ant-layout-content{flex:auto;min-height:0}
|
||||||
.ant-layout-sider{position:relative;min-width:0;background:#001529;transition:all .2s}
|
.ant-layout-sider{position:relative;min-width:0;background:#001529;transition:all .2s}
|
||||||
.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}
|
.ant-layout-sider-children{height:100%;margin-top:-.1px;padding:0.5rem}
|
||||||
.ant-layout-sider-has-trigger{padding-bottom:48px}
|
.ant-layout-sider-has-trigger{padding-bottom:48px}
|
||||||
.ant-layout-sider-right{order:1}
|
.ant-layout-sider-right{order:1}
|
||||||
.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;transition:all .2s}
|
.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;transition:all .2s}
|
||||||
@@ -3278,7 +3280,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:4px 0 0 4px}
|
.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:4px 0 0 4px}
|
||||||
.ant-layout-sider-light{background:#fff}
|
.ant-layout-sider-light{background:#fff}
|
||||||
.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}
|
.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}
|
||||||
.ant-list{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}
|
.ant-list{box-sizing:border-box;margin:1.5em;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}
|
||||||
.ant-list *{outline:0}
|
.ant-list *{outline:0}
|
||||||
.ant-list-pagination{margin-top:24px;text-align:right}
|
.ant-list-pagination{margin-top:24px;text-align:right}
|
||||||
.ant-list-pagination .ant-pagination-options{text-align:left}
|
.ant-list-pagination .ant-pagination-options{text-align:left}
|
||||||
@@ -3303,7 +3305,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-list-footer,.ant-list-header{background:0 0}
|
.ant-list-footer,.ant-list-header{background:0 0}
|
||||||
.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}
|
.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}
|
||||||
.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}
|
.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}
|
||||||
.ant-list-split .ant-list-item{border-bottom:1px solid #e8e8e8}
|
.ant-list-split .ant-list-item{border-bottom:1px solid rgb(153 153 153 / 20%)}
|
||||||
.ant-list-split .ant-list-item:last-child{border-bottom:none}
|
.ant-list-split .ant-list-item:last-child{border-bottom:none}
|
||||||
.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}
|
.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}
|
||||||
.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}
|
.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}
|
||||||
@@ -3530,9 +3532,9 @@ to{max-height:0;padding:0;opacity:0}
|
|||||||
.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:rgba(0,0,0,.45);font-weight:700;line-height:1;text-decoration:none;background:0 0;border:0;outline:0;cursor:pointer;transition:color .3s}
|
.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:rgba(0,0,0,.45);font-weight:700;line-height:1;text-decoration:none;background:0 0;border:0;outline:0;cursor:pointer;transition:color .3s}
|
||||||
.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}
|
.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}
|
||||||
.ant-modal-close:focus,.ant-modal-close:hover{color:rgba(0,0,0,.75);text-decoration:none}
|
.ant-modal-close:focus,.ant-modal-close:hover{color:rgba(0,0,0,.75);text-decoration:none}
|
||||||
.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:4px 4px 0 0}
|
.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid rgba(100, 100, 100, 0.2);border-radius:4px 4px 0 0}
|
||||||
.ant-modal-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}
|
.ant-modal-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}
|
||||||
.ant-modal-footer{padding:10px 16px;text-align:right;background:0 0;border-top:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
.ant-modal-footer{padding:10px 16px;text-align:right;background:0 0;border-top:1px solid rgba(100, 100, 100, 0.2);border-radius:0 0 4px 4px}
|
||||||
.ant-modal-footer button+button{margin-bottom:0;margin-left:8px}
|
.ant-modal-footer button+button{margin-bottom:0;margin-left:8px}
|
||||||
.ant-modal.zoom-appear,.ant-modal.zoom-enter{transform:none;opacity:0;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
.ant-modal.zoom-appear,.ant-modal.zoom-enter{transform:none;opacity:0;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:rgba(0,0,0,.45);filter:alpha(opacity=50)}
|
.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:rgba(0,0,0,.45);filter:alpha(opacity=50)}
|
||||||
@@ -3671,14 +3673,15 @@ to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}
|
|||||||
.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{top:12px}
|
.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{top:12px}
|
||||||
.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}
|
.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}
|
||||||
.ant-progress{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}
|
.ant-progress{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}
|
||||||
|
.ant-progress{box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;}
|
||||||
.ant-progress-line{position:relative;width:100%;font-size:14px}
|
.ant-progress-line{position:relative;width:100%;font-size:14px}
|
||||||
.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}
|
.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}
|
||||||
.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}
|
.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}
|
||||||
.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}
|
.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}
|
||||||
.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#f5f5f5;border-radius:100px}
|
.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#f5f5f5;border-radius:100px}
|
||||||
.ant-progress-circle-trail{stroke:#f5f5f5}
|
.ant-progress-circle-trail{stroke:rgb(100 100 100 / 15%) !important}
|
||||||
.ant-progress-circle-path{-webkit-animation:ant-progress-appear .3s;animation:ant-progress-appear .3s}
|
.ant-progress-circle-path{-webkit-animation:ant-progress-appear .3s;animation:ant-progress-appear .3s}
|
||||||
.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#1890ff}
|
.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:rgb(0 150 112) !important}
|
||||||
.ant-progress-bg,.ant-progress-success-bg{position:relative;background-color:#1890ff;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}
|
.ant-progress-bg,.ant-progress-success-bg{position:relative;background-color:#1890ff;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}
|
||||||
.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#52c41a}
|
.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#52c41a}
|
||||||
.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:rgba(0,0,0,.45);font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}
|
.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:rgba(0,0,0,.45);font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}
|
||||||
@@ -3966,7 +3969,7 @@ to{background-position:0 50%}
|
|||||||
.ant-switch-small.ant-switch-checked .ant-switch-inner{margin-right:18px;margin-left:3px}
|
.ant-switch-small.ant-switch-checked .ant-switch-inner{margin-right:18px;margin-left:3px}
|
||||||
.ant-switch-small.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-13px}
|
.ant-switch-small.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-13px}
|
||||||
.ant-switch-small.ant-switch-loading .ant-switch-loading-icon{font-weight:700;transform:scale(.66667)}
|
.ant-switch-small.ant-switch-loading .ant-switch-loading-icon{font-weight:700;transform:scale(.66667)}
|
||||||
.ant-switch-checked{background-color:#1890ff}
|
.ant-switch-checked{background-color:rgb(0 150 112)}
|
||||||
.ant-switch-checked .ant-switch-inner{margin-right:24px;margin-left:6px}
|
.ant-switch-checked .ant-switch-inner{margin-right:24px;margin-left:6px}
|
||||||
.ant-switch-checked:after{left:100%;margin-left:-1px;transform:translateX(-100%)}
|
.ant-switch-checked:after{left:100%;margin-left:-1px;transform:translateX(-100%)}
|
||||||
.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-19px}
|
.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-19px}
|
||||||
@@ -3988,7 +3991,7 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
|||||||
.ant-table-empty .ant-table-body{overflow-x:auto!important;overflow-y:hidden!important}
|
.ant-table-empty .ant-table-body{overflow-x:auto!important;overflow-y:hidden!important}
|
||||||
.ant-table table{width:100%;text-align:left;border-radius:4px 4px 0 0;border-collapse:separate;border-spacing:0}
|
.ant-table table{width:100%;text-align:left;border-radius:4px 4px 0 0;border-collapse:separate;border-spacing:0}
|
||||||
.ant-table-layout-fixed table{table-layout:fixed}
|
.ant-table-layout-fixed table{table-layout:fixed}
|
||||||
.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:1px solid #e8e8e8;transition:background .3s ease}
|
.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:2px solid rgba(155, 155, 155, 0.15);transition:background .3s ease}
|
||||||
.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}
|
.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}
|
||||||
.ant-table-thead>tr>th .ant-table-filter-icon,.ant-table-thead>tr>th .anticon-filter{position:absolute;top:0;right:0;width:28px;height:100%;color:#bfbfbf;font-size:12px;text-align:center;cursor:pointer;transition:all .3s}
|
.ant-table-thead>tr>th .ant-table-filter-icon,.ant-table-thead>tr>th .anticon-filter{position:absolute;top:0;right:0;width:28px;height:100%;color:#bfbfbf;font-size:12px;text-align:center;cursor:pointer;transition:all .3s}
|
||||||
.ant-table-thead>tr>th .ant-table-filter-icon>svg,.ant-table-thead>tr>th .anticon-filter>svg{position:absolute;top:50%;left:50%;margin-top:-5px;margin-left:-6px}
|
.ant-table-thead>tr>th .ant-table-filter-icon>svg,.ant-table-thead>tr>th .anticon-filter>svg{position:absolute;top:50%;left:50%;margin-top:-5px;margin-left:-6px}
|
||||||
@@ -4049,7 +4052,7 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
|||||||
.ant-table-bordered.ant-table-fixed-header .ant-table-body-inner>table,.ant-table-bordered.ant-table-fixed-header .ant-table-header+.ant-table-body>table{border-top:0}
|
.ant-table-bordered.ant-table-fixed-header .ant-table-body-inner>table,.ant-table-bordered.ant-table-fixed-header .ant-table-header+.ant-table-body>table{border-top:0}
|
||||||
.ant-table-bordered .ant-table-thead>tr:not(:last-child)>th{border-bottom:1px solid #e8e8e8}
|
.ant-table-bordered .ant-table-thead>tr:not(:last-child)>th{border-bottom:1px solid #e8e8e8}
|
||||||
.ant-table-bordered .ant-table-tbody>tr>td,.ant-table-bordered .ant-table-thead>tr>th{border-right:1px solid #e8e8e8}
|
.ant-table-bordered .ant-table-tbody>tr>td,.ant-table-bordered .ant-table-thead>tr>th{border-right:1px solid #e8e8e8}
|
||||||
.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:2px solid rgba(155, 155, 155, 0.15);border-bottom:2px solid rgba(155, 155, 155, 0.15);border-radius:0 0 4px 4px}
|
||||||
.ant-table-pagination.ant-pagination{float:right;margin:16px 0}
|
.ant-table-pagination.ant-pagination{float:right;margin:16px 0}
|
||||||
.ant-table-filter-dropdown{position:relative;min-width:96px;margin-left:-8px;background:#fff;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
.ant-table-filter-dropdown{position:relative;min-width:96px;margin-left:-8px;background:#fff;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||||||
.ant-table-filter-dropdown .ant-dropdown-menu{max-height:calc(100vh - 130px);overflow-x:hidden;border:0;border-radius:4px 4px 0 0;box-shadow:none}
|
.ant-table-filter-dropdown .ant-dropdown-menu{max-height:calc(100vh - 130px);overflow-x:hidden;border:0;border-radius:4px 4px 0 0;box-shadow:none}
|
||||||
@@ -4072,8 +4075,8 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
|||||||
.ant-table-selection-down{display:inline-block;padding:0;line-height:1;cursor:pointer}
|
.ant-table-selection-down{display:inline-block;padding:0;line-height:1;cursor:pointer}
|
||||||
.ant-table-selection-down:hover .anticon-down{color:rgba(0,0,0,.6)}
|
.ant-table-selection-down:hover .anticon-down{color:rgba(0,0,0,.6)}
|
||||||
.ant-table-row-expand-icon{color:#1890ff;text-decoration:none;cursor:pointer;transition:color .3s;display:inline-block;width:17px;height:17px;color:inherit;line-height:13px;text-align:center;background:#fff;border:1px solid #e8e8e8;border-radius:2px;outline:0;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
.ant-table-row-expand-icon{color:#1890ff;text-decoration:none;cursor:pointer;transition:color .3s;display:inline-block;width:17px;height:17px;color:inherit;line-height:13px;text-align:center;background:#fff;border:1px solid #e8e8e8;border-radius:2px;outline:0;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#40a9ff}
|
.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:rgb(0 150 112)}
|
||||||
.ant-table-row-expand-icon:active{color:#096dd9}
|
.ant-table-row-expand-icon:active{color:rgb(0 150 112)}
|
||||||
.ant-table-row-expand-icon:active,.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{border-color:currentColor}
|
.ant-table-row-expand-icon:active,.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{border-color:currentColor}
|
||||||
.ant-table-row-expanded:after{content:"-"}
|
.ant-table-row-expanded:after{content:"-"}
|
||||||
.ant-table-row-collapsed:after{content:"+"}
|
.ant-table-row-collapsed:after{content:"+"}
|
||||||
@@ -4491,4 +4494,4 @@ to{width:0;height:0;margin:0;padding:0;opacity:0}
|
|||||||
}
|
}
|
||||||
@keyframes uploadAnimateInlineOut{
|
@keyframes uploadAnimateInlineOut{
|
||||||
to{width:0;height:0;margin:0;padding:0;opacity:0}
|
to{width:0;height:0;margin:0;padding:0;opacity:0}
|
||||||
}
|
}
|
||||||
|
|||||||
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
@@ -1,5 +1,22 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
@@ -10,8 +27,14 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-layout-sider {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-card {
|
||||||
border-radius: 30px;
|
border-radius: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
@@ -169,7 +192,7 @@
|
|||||||
.ant-layout-sider-zero-width-trigger,
|
.ant-layout-sider-zero-width-trigger,
|
||||||
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
||||||
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
||||||
background:#161b22
|
background:#1A202B
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark {
|
.ant-card-dark {
|
||||||
@@ -180,9 +203,35 @@
|
|||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-card-dark:hover {
|
||||||
border-color: #e8e8e8;
|
border-color: #e8e8e8;
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-setting-textarea {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
min-height: 300px !important;
|
||||||
|
/*max-height: 800px !important;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark-box-nohover{
|
||||||
|
padding: 0 20px 20px !important;
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||||
|
}
|
||||||
|
.ant-card-dark-box-nohover:hover{
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||||
|
/*background-color: rgb(36 44 58 / 50%);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark-securitybox-nohover{
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||||
|
}
|
||||||
|
.ant-card-dark-securitybox-nohover:hover{
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .ant-card-bordered:hover {
|
||||||
|
box-shadow: 0 3px 12px -0.8px #0000005c;
|
||||||
|
} */
|
||||||
|
|
||||||
.ant-card-dark .ant-table-thead th {
|
.ant-card-dark .ant-table-thead th {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #161b22;
|
background-color: #161b22;
|
||||||
@@ -199,6 +248,7 @@
|
|||||||
.ant-card-dark .ant-input-group-addon {
|
.ant-card-dark .ant-input-group-addon {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #262f3d;
|
background-color: #262f3d;
|
||||||
|
border: 1px solid rgb(149 149 149 / 30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-list-item-meta-title,
|
.ant-card-dark .ant-list-item-meta-title,
|
||||||
@@ -222,20 +272,27 @@
|
|||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td {
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
|
||||||
.ant-card-dark .ant-calendar-date:hover,
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
|
||||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
|
||||||
background-color: #11314d;
|
background-color: #11314d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||||
|
.ant-card-dark .ant-calendar-date:hover,
|
||||||
|
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||||
|
background-color: rgb(4, 119, 90);
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card-dark tbody .ant-table-expanded-row,
|
.ant-card-dark tbody .ant-table-expanded-row,
|
||||||
.ant-card-dark .ant-calendar-time-picker-inner {
|
.ant-card-dark .ant-calendar-time-picker-inner {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #1a212a;
|
background-color: #1a212a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-input-number {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-input,
|
.ant-card-dark .ant-input,
|
||||||
.ant-card-dark .ant-input-number,
|
.ant-card-dark .ant-input-number,
|
||||||
.ant-card-dark .ant-input-number-handler-wrap,
|
.ant-card-dark .ant-input-number-handler-wrap,
|
||||||
@@ -244,7 +301,17 @@
|
|||||||
.ant-card-dark .ant-select-selection,
|
.ant-card-dark .ant-select-selection,
|
||||||
.ant-card-dark .ant-calendar-picker-clear {
|
.ant-card-dark .ant-calendar-picker-clear {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #193752;
|
background-color: rgb(46 59 82 / 50%);
|
||||||
|
border: 1px solid rgb(0 150 112 / 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout:not(.login) .ant-input:focus,
|
||||||
|
.ant-layout:not(.login) .ant-input:hover,
|
||||||
|
.ant-layout:not(.login) .ant-input-number:focus,
|
||||||
|
.ant-layout:not(.login) .ant-input-number:hover,
|
||||||
|
.ant-layout:not(.login) .ant-calendar-input:focus,
|
||||||
|
.ant-layout:not(.login) .ant-calendar-input:hover {
|
||||||
|
background-color: rgba(0, 149, 111, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
@@ -255,11 +322,12 @@
|
|||||||
.ant-card-dark .ant-collapse-item {
|
.ant-card-dark .ant-collapse-item {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #161b22;
|
background-color: #161b22;
|
||||||
|
border-radius: 0.5rem 0.5rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dropdown-menu-dark,
|
.ant-dropdown-menu-dark,
|
||||||
.ant-card-dark .ant-modal-content {
|
.ant-card-dark .ant-modal-content {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
border:1px solid rgb(100 100 100 / 20%);
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +339,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
background-color: #1668dc;
|
background-color: rgb(0 150 112);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
||||||
@@ -324,9 +392,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-green {
|
.ant-card-dark .ant-tag-green {
|
||||||
color: #6abe39;
|
color: #37b998;
|
||||||
background: #162312;
|
background: #101e1a;
|
||||||
border-color: #274916;
|
border-color: #144237;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-tag-cyan {
|
.ant-card-dark .ant-tag-cyan {
|
||||||
@@ -353,7 +421,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-switch-checked {
|
.ant-card-dark .ant-switch-checked {
|
||||||
background-color: #0c61b0;
|
background-color: #009670;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-btn,
|
.ant-card-dark .ant-btn,
|
||||||
@@ -364,19 +432,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-radio-button-wrapper:hover {
|
.ant-card-dark .ant-radio-button-wrapper:hover {
|
||||||
color: #177ddc;
|
color: #009670;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-btn-primary {
|
.ant-card-dark .ant-btn-primary {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #073763;
|
background-color: rgb(7 98 75 / 50%);
|
||||||
border-color: #1890ff;
|
border-color: #009670;
|
||||||
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
||||||
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
||||||
}
|
}
|
||||||
.ant-card-dark .ant-btn-primary:hover {
|
.ant-card-dark .ant-btn-primary:hover {
|
||||||
background-color: #40a9ff;
|
background-color: #009670;
|
||||||
border-color: #40a9ff;
|
border-color: #40a9ff00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-dark .ant-popover-content {
|
.ant-dark .ant-popover-content {
|
||||||
@@ -396,4 +464,21 @@
|
|||||||
|
|
||||||
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
|
.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow {
|
||||||
border-color: transparent #2e3b52 #2e3b52 transparent;
|
border-color: transparent #2e3b52 #2e3b52 transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: rgb(50 62 82 / 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(133 133 133 / 80%);
|
||||||
|
border-radius: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #919191;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded
|
|||||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
|
||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
config => {
|
(config) => {
|
||||||
config.data = Qs.stringify(config.data, {
|
if (config.data instanceof FormData) {
|
||||||
arrayFormat: 'repeat'
|
config.headers['Content-Type'] = 'multipart/form-data';
|
||||||
});
|
} else {
|
||||||
|
config.data = Qs.stringify(config.data, {
|
||||||
|
arrayFormat: 'repeat',
|
||||||
|
});
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
error => Promise.reject(error)
|
(error) => Promise.reject(error),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,36 +1,41 @@
|
|||||||
supportLangs = [
|
const supportLangs = [
|
||||||
{
|
{
|
||||||
name : "English",
|
name: 'English',
|
||||||
value : "en-US",
|
value: 'en-US',
|
||||||
icon : "🇺🇸"
|
icon: '🇺🇸',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Farsi",
|
name: 'فارسی',
|
||||||
value : "fa_IR",
|
value: 'fa_IR',
|
||||||
icon : "🇮🇷"
|
icon: '🇮🇷',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "汉语",
|
name: '汉语',
|
||||||
value : "zh-Hans",
|
value: 'zh-Hans',
|
||||||
icon : "🇨🇳"
|
icon: '🇨🇳',
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
name: 'Русский',
|
||||||
|
value: 'ru_RU',
|
||||||
|
icon: '🇷🇺',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function getLang(){
|
function getLang() {
|
||||||
let lang = getCookie('lang')
|
let lang = 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 (isSupportLang(lang)){
|
if (isSupportLang(lang)) {
|
||||||
setCookie('lang' , lang , 150)
|
setCookie('lang', lang, 150);
|
||||||
}else{
|
} else {
|
||||||
setCookie('lang' , 'en-US' , 150)
|
setCookie('lang', 'en-US', 150);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
setCookie('lang' , 'en-US' , 150)
|
setCookie('lang', 'en-US', 150);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,47 +43,21 @@ function getLang(){
|
|||||||
return lang;
|
return lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLang(lang){
|
function setLang(lang) {
|
||||||
|
if (!isSupportLang(lang)) {
|
||||||
if (!isSupportLang(lang)){
|
|
||||||
lang = 'en-US';
|
lang = 'en-US';
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie('lang' , lang , 150)
|
setCookie('lang', lang, 150);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSupportLang(lang){
|
function isSupportLang(lang) {
|
||||||
for (l of supportLangs){
|
for (l of supportLangs) {
|
||||||
if (l.value === lang){
|
if (l.value === lang) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function getCookie(cname) {
|
|
||||||
let name = cname + "=";
|
|
||||||
let decodedCookie = decodeURIComponent(document.cookie);
|
|
||||||
let ca = decodedCookie.split(';');
|
|
||||||
for(let i = 0; i <ca.length; i++) {
|
|
||||||
let c = ca[i];
|
|
||||||
while (c.charAt(0) == ' ') {
|
|
||||||
c = c.substring(1);
|
|
||||||
}
|
|
||||||
if (c.indexOf(name) == 0) {
|
|
||||||
return c.substring(name.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCookie(cname, cvalue, exdays) {
|
|
||||||
const d = new Date();
|
|
||||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
|
||||||
let expires = "expires="+ d.toUTCString();
|
|
||||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
|
||||||
}
|
|
||||||
@@ -153,9 +153,9 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(clientIndex) {
|
genLink(address=this.address, remark=this.remark, clientIndex=0) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(this.address, this.remark, clientIndex);
|
return inbound.genLink(address, remark, clientIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
get genInboundLinks() {
|
get genInboundLinks() {
|
||||||
@@ -181,8 +181,17 @@ class AllSetting {
|
|||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
|
this.tgLang = "en-US";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
this.secretEnable = false;
|
this.secretEnable = false;
|
||||||
|
this.subEnable = false;
|
||||||
|
this.subListen = "";
|
||||||
|
this.subPort = "2096";
|
||||||
|
this.subPath = "sub/";
|
||||||
|
this.subDomain = "";
|
||||||
|
this.subCertFile = "";
|
||||||
|
this.subKeyFile = "";
|
||||||
|
this.subUpdates = 0;
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,8 @@ const VmessMethods = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
|
||||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
|
||||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
const XTLS_FLOW_CONTROL = {
|
||||||
@@ -489,8 +482,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert() {
|
||||||
this.certs.push(cert);
|
this.certs.push(new TlsStreamSettings.Cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCert(index) {
|
removeCert(index) {
|
||||||
@@ -505,8 +498,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName, json.settings.domains); }
|
||||||
}
|
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
json.minVersion,
|
json.minVersion,
|
||||||
@@ -573,17 +565,19 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '', serverName = '') {
|
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
|
this.domains = domains;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.servername,
|
json.serverName,
|
||||||
|
json.domains,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -591,6 +585,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
|
domains: this.domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -606,8 +601,8 @@ class XtlsStreamSettings extends XrayCommonClass {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert() {
|
||||||
this.certs.push(cert);
|
this.certs.push(new XtlsStreamSettings.Cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCert(index) {
|
removeCert(index) {
|
||||||
@@ -713,7 +708,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
minClient = '',
|
minClient = '',
|
||||||
maxClient = '',
|
maxClient = '',
|
||||||
maxTimediff = 0,
|
maxTimediff = 0,
|
||||||
shortIds = RandomUtil.randowShortId(),
|
shortIds = RandomUtil.randomShortId(),
|
||||||
settings= new RealityStreamSettings.Settings()
|
settings= new RealityStreamSettings.Settings()
|
||||||
){
|
){
|
||||||
super();
|
super();
|
||||||
@@ -732,7 +727,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
let settings;
|
let settings;
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
|
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName, json.settings.spiderX);
|
||||||
}
|
}
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
json.show,
|
json.show,
|
||||||
@@ -765,17 +760,19 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') {
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
|
this.spiderX = spiderX;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new RealityStreamSettings.Settings(
|
return new RealityStreamSettings.Settings(
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.serverName,
|
json.serverName,
|
||||||
|
json.spiderX,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -783,6 +780,7 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
|
spiderX: this.spiderX,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -939,7 +937,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = Inbound.Settings.getSettings(protocol);
|
this.settings = Inbound.Settings.getSettings(protocol);
|
||||||
if (protocol === Protocols.TROJAN) {
|
if (protocol === Protocols.TROJAN) {
|
||||||
this.tls = true;
|
this.tls = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,66 +1010,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.network === "http";
|
return this.network === "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMess & VLess
|
|
||||||
get uuid() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].id;
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].id;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VLess & Trojan
|
|
||||||
get flow() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].flow;
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].flow;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VMess
|
|
||||||
get alterId() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].alterId;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Socks & HTTP
|
|
||||||
get username() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].user;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trojan & Shadowsocks & Socks & HTTP
|
|
||||||
get password() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].password;
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return this.settings.password;
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].pass;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
get method() {
|
get method() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -1146,9 +1084,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if(this.settings.trojans[index].expiryTime > 0)
|
if(this.settings.trojans[index].expiryTime > 0)
|
||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
if(this.settings.shadowsockses[index].expiryTime > 0)
|
||||||
|
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1101,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1228,7 +1169,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1413,6 +1353,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.xtls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
|
if (this.stream.xtls.settings.serverName !== ''){
|
||||||
|
params.set("sni", this.stream.xtls.settings.serverName);
|
||||||
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1432,6 +1375,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `vless://${uuid}@${address}:${port}`;
|
const link = `vless://${uuid}@${address}:${port}`;
|
||||||
@@ -1443,13 +1389,11 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', remark='') {
|
genSSLink(address='', remark='', clientIndex = 0) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const server = this.stream.tls.server;
|
const port = this.port;
|
||||||
if (!ObjectUtil.isEmpty(server)) {
|
|
||||||
address = server;
|
return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark);
|
||||||
}
|
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1534,6 +1478,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
if (this.xtls) {
|
||||||
@@ -1545,10 +1492,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.xtls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
|
if (this.stream.xtls.settings.serverName !== ''){
|
||||||
|
params.set("sni", this.stream.xtls.settings.serverName);
|
||||||
|
}
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
url.searchParams.set(key, value)
|
url.searchParams.set(key, value)
|
||||||
@@ -1559,21 +1509,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', remark='', clientIndex=0) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
if (this.settings.vmesses[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.vmesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, remark, clientIndex);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
if (this.settings.vlesses[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.vlesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.genSSLink(address, remark, clientIndex);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.trojans[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, remark, clientIndex);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
@@ -1585,12 +1527,17 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
JSON.parse(this.settings).clients.forEach((_,index) => {
|
case Protocols.SHADOWSOCKS:
|
||||||
link += this.genLink(address, remark, index) + '\r\n';
|
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||||
|
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||||
|
this.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
link += this.genLink(domain.domain, remark + '-' + client.email + '-' + domain.remark, index) + '\r\n';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link += this.genLink(address, remark + '-' + client.email, index) + '\r\n';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return link;
|
return link;
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return (this.genSSLink(address, remark) + '\r\n');
|
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2033,13 +1980,15 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
|||||||
Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
method=SSMethods.BLAKE3_AES_256_GCM,
|
method=SSMethods.BLAKE3_AES_256_GCM,
|
||||||
password=RandomUtil.randomSeq(44),
|
password=RandomUtil.randomShadowsocksPassword(),
|
||||||
network='tcp,udp'
|
network='tcp,udp',
|
||||||
|
shadowsockses=[new Inbound.ShadowsocksSettings.Shadowsocks()]
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
|
this.shadowsockses = shadowsockses;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -2048,6 +1997,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
json.method,
|
json.method,
|
||||||
json.password,
|
json.password,
|
||||||
json.network,
|
json.network,
|
||||||
|
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2056,10 +2006,77 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
method: this.method,
|
method: this.method,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
|
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
|
super();
|
||||||
|
this.password = password;
|
||||||
|
this.email = email;
|
||||||
|
this.limitIp = limitIp;
|
||||||
|
this.totalGB = totalGB;
|
||||||
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
password: this.password,
|
||||||
|
email: this.email,
|
||||||
|
limitIp: this.limitIp,
|
||||||
|
totalGB: this.totalGB,
|
||||||
|
expiryTime: this.expiryTime,
|
||||||
|
enable: this.enable,
|
||||||
|
tgId: this.tgId,
|
||||||
|
subId: this.subId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||||
|
json.password,
|
||||||
|
json.email,
|
||||||
|
json.limitIp,
|
||||||
|
json.totalGB,
|
||||||
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get _expiryTime() {
|
||||||
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
|
return moment(this.expiryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _expiryTime(t) {
|
||||||
|
if (t == null || t === "") {
|
||||||
|
this.expiryTime = 0;
|
||||||
|
} else {
|
||||||
|
this.expiryTime = t.valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get _totalGB() {
|
||||||
|
return toFixed(this.totalGB / ONE_GB, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _totalGB(gb) {
|
||||||
|
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -56,14 +56,63 @@ function toFixed(num, n) {
|
|||||||
return Math.round(num * n) / n;
|
return Math.round(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce (fn, delay) {
|
function debounce(fn, delay) {
|
||||||
var timeoutID = null
|
var timeoutID = null;
|
||||||
return function () {
|
return function () {
|
||||||
clearTimeout(timeoutID)
|
clearTimeout(timeoutID);
|
||||||
var args = arguments
|
var args = arguments;
|
||||||
var that = this
|
var that = this;
|
||||||
timeoutID = setTimeout(function () {
|
timeoutID = setTimeout(function () {
|
||||||
fn.apply(that, args)
|
fn.apply(that, args);
|
||||||
}, delay)
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(cname) {
|
||||||
|
let name = cname + '=';
|
||||||
|
let ca = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
// decode cookie value only
|
||||||
|
return decodeURIComponent(c.substring(name.length, c.length));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setCookie(cname, cvalue, exdays) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||||
|
let expires = 'expires=' + d.toUTCString();
|
||||||
|
// encode cookie value
|
||||||
|
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function usageColor(data, threshold, total) {
|
||||||
|
switch (true) {
|
||||||
|
case data === null:
|
||||||
|
return 'blue';
|
||||||
|
case total <= 0:
|
||||||
|
return 'blue';
|
||||||
|
case data < total - threshold:
|
||||||
|
return 'cyan';
|
||||||
|
case data < total:
|
||||||
|
return 'orange';
|
||||||
|
default:
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAllItemsExist(array1, array2) {
|
||||||
|
for (let i = 0; i < array1.length; i++) {
|
||||||
|
if (!array2.includes(array1[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
const oneMinute = 1000 * 60; // 一分钟的毫秒数
|
const oneMinute = 1000 * 60; // MilliseConds in a Minute
|
||||||
const oneHour = oneMinute * 60; // 一小时的毫秒数
|
const oneHour = oneMinute * 60; // The milliseconds of one hour
|
||||||
const oneDay = oneHour * 24; // 一天的毫秒数
|
const oneDay = oneHour * 24; // The Number of MilliseConds A Day
|
||||||
const oneWeek = oneDay * 7; // 一星期的毫秒数
|
const oneWeek = oneDay * 7; // The milliseconds per week
|
||||||
const oneMonth = oneDay * 30; // 一个月的毫秒数
|
const oneMonth = oneDay * 30; // The milliseconds of a month
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按天数减少
|
* Decrease according to the number of days
|
||||||
*
|
*
|
||||||
* @param days 要减少的天数
|
* @param days to reduce the number of days to be reduced
|
||||||
*/
|
*/
|
||||||
Date.prototype.minusDays = function (days) {
|
Date.prototype.minusDays = function (days) {
|
||||||
return this.minusMillis(oneDay * days);
|
return this.minusMillis(oneDay * days);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按天数增加
|
* Increase according to the number of days
|
||||||
*
|
*
|
||||||
* @param days 要增加的天数
|
* @param days The number of days to be increased
|
||||||
*/
|
*/
|
||||||
Date.prototype.plusDays = function (days) {
|
Date.prototype.plusDays = function (days) {
|
||||||
return this.plusMillis(oneDay * days);
|
return this.plusMillis(oneDay * days);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按小时减少
|
* A few
|
||||||
*
|
*
|
||||||
* @param hours 要减少的小时数
|
* @param hours to be reduced
|
||||||
*/
|
*/
|
||||||
Date.prototype.minusHours = function (hours) {
|
Date.prototype.minusHours = function (hours) {
|
||||||
return this.minusMillis(oneHour * hours);
|
return this.minusMillis(oneHour * hours);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按小时增加
|
* Increase hourly
|
||||||
*
|
*
|
||||||
* @param hours 要增加的小时数
|
* @param hours to increase the number of hours
|
||||||
*/
|
*/
|
||||||
Date.prototype.plusHours = function (hours) {
|
Date.prototype.plusHours = function (hours) {
|
||||||
return this.plusMillis(oneHour * hours);
|
return this.plusMillis(oneHour * hours);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按分钟减少
|
* Make reduction in minutes
|
||||||
*
|
*
|
||||||
* @param minutes 要减少的分钟数
|
* @param minutes to reduce the number of minutes
|
||||||
*/
|
*/
|
||||||
Date.prototype.minusMinutes = function (minutes) {
|
Date.prototype.minusMinutes = function (minutes) {
|
||||||
return this.minusMillis(oneMinute * minutes);
|
return this.minusMillis(oneMinute * minutes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按分钟增加
|
* Add in minutes
|
||||||
*
|
*
|
||||||
* @param minutes 要增加的分钟数
|
* @param minutes to increase the number of minutes
|
||||||
*/
|
*/
|
||||||
Date.prototype.plusMinutes = function (minutes) {
|
Date.prototype.plusMinutes = function (minutes) {
|
||||||
return this.plusMillis(oneMinute * minutes);
|
return this.plusMillis(oneMinute * minutes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按毫秒减少
|
* Decrease in milliseconds
|
||||||
*
|
*
|
||||||
* @param millis 要减少的毫秒数
|
* @param millis to reduce the milliseconds
|
||||||
*/
|
*/
|
||||||
Date.prototype.minusMillis = function(millis) {
|
Date.prototype.minusMillis = function(millis) {
|
||||||
let time = this.getTime() - millis;
|
let time = this.getTime() - millis;
|
||||||
@@ -71,9 +71,9 @@ Date.prototype.minusMillis = function(millis) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按毫秒增加
|
* Add in milliseconds to increase
|
||||||
*
|
*
|
||||||
* @param millis 要增加的毫秒数
|
* @param millis to increase the milliseconds to increase
|
||||||
*/
|
*/
|
||||||
Date.prototype.plusMillis = function(millis) {
|
Date.prototype.plusMillis = function(millis) {
|
||||||
let time = this.getTime() + millis;
|
let time = this.getTime() + millis;
|
||||||
@@ -83,7 +83,7 @@ Date.prototype.plusMillis = function(millis) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置时间为当天的 00:00:00.000
|
* Setting time is 00: 00: 00.000 on the day
|
||||||
*/
|
*/
|
||||||
Date.prototype.setMinTime = function () {
|
Date.prototype.setMinTime = function () {
|
||||||
this.setHours(0);
|
this.setHours(0);
|
||||||
@@ -94,7 +94,7 @@ Date.prototype.setMinTime = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置时间为当天的 23:59:59.999
|
* Setting time is 23: 59: 59.999 on the same day
|
||||||
*/
|
*/
|
||||||
Date.prototype.setMaxTime = function () {
|
Date.prototype.setMaxTime = function () {
|
||||||
this.setHours(23);
|
this.setHours(23);
|
||||||
@@ -105,37 +105,36 @@ Date.prototype.setMaxTime = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化日期
|
* Formatting date
|
||||||
*/
|
*/
|
||||||
Date.prototype.formatDate = function () {
|
Date.prototype.formatDate = function () {
|
||||||
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate());
|
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化时间
|
* Format time
|
||||||
*/
|
*/
|
||||||
Date.prototype.formatTime = function () {
|
Date.prototype.formatTime = function () {
|
||||||
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds());
|
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化日期加时间
|
* Formatting date plus time
|
||||||
*
|
*
|
||||||
* @param split 日期和时间之间的分隔符,默认是一个空格
|
* @param split Date and time separation symbols, default is a space
|
||||||
*/
|
*/
|
||||||
Date.prototype.formatDateTime = function (split = ' ') {
|
Date.prototype.formatDateTime = function (split = ' ') {
|
||||||
return this.formatDate() + split + this.formatTime();
|
return this.formatDate() + split + this.formatTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
class DateUtil {
|
class DateUtil {
|
||||||
|
// String string to date object
|
||||||
// 字符串转 Date 对象
|
|
||||||
static parseDate(str) {
|
static parseDate(str) {
|
||||||
return new Date(str.replace(/-/g, '/'));
|
return new Date(str.replace(/-/g, '/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
static formatMillis(millis) {
|
static formatMillis(millis) {
|
||||||
return moment(millis).format('YYYY-M-D H:m:s')
|
return moment(millis).format('YYYY-M-D H:m:s');
|
||||||
}
|
}
|
||||||
|
|
||||||
static firstDayOfMonth() {
|
static firstDayOfMonth() {
|
||||||
@@ -144,4 +143,4 @@ class DateUtil {
|
|||||||
date.setMinTime();
|
date.setMinTime();
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,36 +68,20 @@ class HttpUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PromiseUtil {
|
class PromiseUtil {
|
||||||
|
|
||||||
static async sleep(timeout) {
|
static async sleep(timeout) {
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
setTimeout(resolve, timeout)
|
setTimeout(resolve, timeout)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const seq = [
|
const seq = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
|
||||||
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
||||||
'o', 'p', 'q', 'r', 's', 't',
|
|
||||||
'u', 'v', 'w', 'x', 'y', 'z',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
|
||||||
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
||||||
'O', 'P', 'Q', 'R', 'S', 'T',
|
|
||||||
'U', 'V', 'W', 'X', 'Y', 'Z'
|
|
||||||
];
|
|
||||||
|
|
||||||
const shortIdSeq = [
|
const shortIdSeq = 'abcdef0123456789'.split('');
|
||||||
'a', 'b', 'c', 'd', 'e', 'f',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
];
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
|
|
||||||
static randomIntRange(min, max) {
|
static randomIntRange(min, max) {
|
||||||
return parseInt(Math.random() * (max - min) + min, 10);
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomInt(n) {
|
static randomInt(n) {
|
||||||
@@ -119,6 +103,10 @@ class RandomUtil {
|
|||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randomShortId() {
|
||||||
|
return this.randomShortIdSeq(8);
|
||||||
|
}
|
||||||
|
|
||||||
static randomLowerAndNum(count) {
|
static randomLowerAndNum(count) {
|
||||||
let str = '';
|
let str = '';
|
||||||
@@ -153,22 +141,20 @@ class RandomUtil {
|
|||||||
static randomText() {
|
static randomText() {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
var len = 6 + Math.floor(Math.random() * 5)
|
for (var ii = 0; ii < 8; ii++) {
|
||||||
for(var ii=0; ii<len; ii++){
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
string += chars[Math.floor(Math.random() * chars.length)];
|
||||||
}
|
}
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randowShortId() {
|
static randomShadowsocksPassword() {
|
||||||
let str = '';
|
let array = new Uint8Array(32);
|
||||||
str += this.randomShortIdSeq(8)
|
window.crypto.getRandomValues(array);
|
||||||
return str;
|
return btoa(String.fromCharCode.apply(null, array));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|
||||||
static getPropIgnoreCase(obj, prop) {
|
static getPropIgnoreCase(obj, prop) {
|
||||||
for (const name in obj) {
|
for (const name in obj) {
|
||||||
if (!obj.hasOwnProperty(name)) {
|
if (!obj.hasOwnProperty(name)) {
|
||||||
@@ -316,5 +302,4 @@ class ObjectUtil {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||||
@@ -14,7 +19,7 @@ func NewAPIController(g *gin.RouterGroup) *APIController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/xui/API/inbounds")
|
g = g.Group("/panel/api/inbounds")
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
|
|
||||||
g.GET("/list", a.getAllInbounds)
|
g.GET("/list", a.getAllInbounds)
|
||||||
@@ -32,24 +37,30 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
g.GET("/createbackup", a.createBackup)
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) getAllInbounds(c *gin.Context) {
|
func (a *APIController) getAllInbounds(c *gin.Context) {
|
||||||
a.inboundController.getInbounds(c)
|
a.inboundController.getInbounds(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) getSingleInbound(c *gin.Context) {
|
func (a *APIController) getSingleInbound(c *gin.Context) {
|
||||||
a.inboundController.getInbound(c)
|
a.inboundController.getInbound(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) getClientTraffics(c *gin.Context) {
|
func (a *APIController) getClientTraffics(c *gin.Context) {
|
||||||
a.inboundController.getClientTraffics(c)
|
a.inboundController.getClientTraffics(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) addInbound(c *gin.Context) {
|
func (a *APIController) addInbound(c *gin.Context) {
|
||||||
a.inboundController.addInbound(c)
|
a.inboundController.addInbound(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) delInbound(c *gin.Context) {
|
func (a *APIController) delInbound(c *gin.Context) {
|
||||||
a.inboundController.delInbound(c)
|
a.inboundController.delInbound(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) updateInbound(c *gin.Context) {
|
func (a *APIController) updateInbound(c *gin.Context) {
|
||||||
a.inboundController.updateInbound(c)
|
a.inboundController.updateInbound(c)
|
||||||
}
|
}
|
||||||
@@ -61,24 +72,35 @@ func (a *APIController) getClientIps(c *gin.Context) {
|
|||||||
func (a *APIController) clearClientIps(c *gin.Context) {
|
func (a *APIController) clearClientIps(c *gin.Context) {
|
||||||
a.inboundController.clearClientIps(c)
|
a.inboundController.clearClientIps(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) addInboundClient(c *gin.Context) {
|
func (a *APIController) addInboundClient(c *gin.Context) {
|
||||||
a.inboundController.addInboundClient(c)
|
a.inboundController.addInboundClient(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) delInboundClient(c *gin.Context) {
|
func (a *APIController) delInboundClient(c *gin.Context) {
|
||||||
a.inboundController.delInboundClient(c)
|
a.inboundController.delInboundClient(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) updateInboundClient(c *gin.Context) {
|
func (a *APIController) updateInboundClient(c *gin.Context) {
|
||||||
a.inboundController.updateInboundClient(c)
|
a.inboundController.updateInboundClient(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
||||||
a.inboundController.resetClientTraffic(c)
|
a.inboundController.resetClientTraffic(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
||||||
a.inboundController.resetAllTraffics(c)
|
a.inboundController.resetAllTraffics(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||||
a.inboundController.resetAllClientTraffics(c)
|
a.inboundController.resetAllClientTraffics(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
a.inboundController.delDepletedClients(c)
|
a.inboundController.delDepletedClients(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
|
a.Tgbot.SendBackupToAdmins()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,7 +15,7 @@ type BaseController struct {
|
|||||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
if !session.IsLogin(c) {
|
if !session.IsLogin(c) {
|
||||||
if isAjax(c) {
|
if isAjax(c) {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.loginAgain"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
@@ -23,11 +25,13 @@ func (a *BaseController) checkLogin(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func I18n(c *gin.Context, name string) string {
|
func I18nWeb(c *gin.Context, name string, params ...string) string {
|
||||||
anyfunc, _ := c.Get("I18n")
|
anyfunc, funcExists := c.Get("I18n")
|
||||||
i18n, _ := anyfunc.(func(key string, params ...string) (string, error))
|
if !funcExists {
|
||||||
|
logger.Warning("I18n function not exists in gin context!")
|
||||||
message, _ := i18n(name)
|
return ""
|
||||||
|
}
|
||||||
return message
|
i18nFunc, _ := anyfunc.(func(i18nType locale.I18nType, key string, keyParams ...string) string)
|
||||||
|
msg := i18nFunc(locale.Web, name, params...)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbounds, nil)
|
jsonObj(c, inbounds, nil)
|
||||||
@@ -68,12 +68,12 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
func (a *InboundController) getInbound(c *gin.Context) {
|
func (a *InboundController) getInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "get"), err)
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err := a.inboundService.GetInbound(id)
|
inbound, err := a.inboundService.GetInbound(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
@@ -93,7 +93,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.addTo"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
@@ -101,7 +101,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound.Enable = true
|
inbound.Enable = true
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
inbound, err = a.inboundService.AddInbound(inbound)
|
inbound, err = a.inboundService.AddInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.addTo"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -110,11 +110,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) delInbound(c *gin.Context) {
|
func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "delete"), err)
|
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelInbound(id)
|
err = a.inboundService.DelInbound(id)
|
||||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
@@ -131,11 +131,11 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = c.ShouldBind(inbound)
|
err = c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
inbound, err = a.inboundService.UpdateInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.revise"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -146,17 +146,24 @@ func (a *InboundController) getClientIps(c *gin.Context) {
|
|||||||
|
|
||||||
ips, err := a.inboundService.GetInboundClientIps(email)
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
jsonObj(c, "Failed to get client IPs", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ips == "" {
|
||||||
jsonObj(c, "No IP Record", nil)
|
jsonObj(c, "No IP Record", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj(c, ips, nil)
|
jsonObj(c, ips, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) clearClientIps(c *gin.Context) {
|
func (a *InboundController) clearClientIps(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
err := a.inboundService.ClearClientIps(email)
|
err := a.inboundService.ClearClientIps(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Revise", err)
|
jsonMsg(c, "Update", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
@@ -165,7 +172,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
data := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(data)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +190,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
@@ -205,7 +212,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +230,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
@@ -251,7 +258,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +273,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelDepletedClients(id)
|
err = a.inboundService.DelDepletedClients(id)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (a *IndexController) index(c *gin.Context) {
|
func (a *IndexController) index(c *gin.Context) {
|
||||||
if session.IsLogin(c) {
|
if session.IsLogin(c) {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "xui/")
|
c.Redirect(http.StatusTemporaryRedirect, "panel/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
html(c, "login.html", "pages.login.title", nil)
|
html(c, "login.html", "pages.login.title", nil)
|
||||||
@@ -49,26 +49,27 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
var form LoginForm
|
var form LoginForm
|
||||||
err := c.ShouldBind(&form)
|
err := c.ShouldBind(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.invalidFormData"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Username == "" {
|
if form.Username == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyUsername"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,14 +78,16 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
logger.Infof("Unable to get session's max age from DB")
|
logger.Infof("Unable to get session's max age from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
if sessionMaxAge > 0 {
|
||||||
if err != nil {
|
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
logger.Infof("Unable to set session's max age")
|
if err != nil {
|
||||||
|
logger.Infof("Unable to set session's max age")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Info("user", user.Id, "login success")
|
logger.Info("user", user.Id, "login success")
|
||||||
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *IndexController) logout(c *gin.Context) {
|
func (a *IndexController) logout(c *gin.Context) {
|
||||||
@@ -101,5 +104,4 @@ func (a *IndexController) getSecretStatus(c *gin.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
jsonObj(c, status, nil)
|
jsonObj(c, status, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -8,6 +11,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
|
||||||
|
|
||||||
type ServerController struct {
|
type ServerController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
@@ -41,6 +46,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
g.POST("/getConfigJson", a.getConfigJson)
|
||||||
g.GET("/getDb", a.getDb)
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +81,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
|||||||
|
|
||||||
versions, err := a.serverService.GetXrayVersions()
|
versions, err := a.serverService.GetXrayVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getVersion"), err)
|
jsonMsg(c, I18nWeb(c, "getVersion"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +94,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
|||||||
func (a *ServerController) installXray(c *gin.Context) {
|
func (a *ServerController) installXray(c *gin.Context) {
|
||||||
version := c.Param("version")
|
version := c.Param("version")
|
||||||
err := a.serverService.UpdateXray(version)
|
err := a.serverService.UpdateXray(version)
|
||||||
jsonMsg(c, I18n(c, "install")+" xray", err)
|
jsonMsg(c, I18nWeb(c, "install")+" xray", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) stopXrayService(c *gin.Context) {
|
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||||
@@ -99,8 +105,8 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray stoped", err)
|
jsonMsg(c, "Xray stoped", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) restartXrayService(c *gin.Context) {
|
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||||
err := a.serverService.RestartXrayService()
|
err := a.serverService.RestartXrayService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,7 +114,6 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray restarted", err)
|
jsonMsg(c, "Xray restarted", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) getLogs(c *gin.Context) {
|
func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
@@ -136,14 +141,49 @@ func (a *ServerController) getDb(c *gin.Context) {
|
|||||||
jsonMsg(c, "get Database", err)
|
jsonMsg(c, "get Database", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename := "x-ui.db"
|
||||||
|
|
||||||
|
if !isValidFilename(filename) {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Set the headers for the response
|
// Set the headers for the response
|
||||||
c.Header("Content-Type", "application/octet-stream")
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
c.Header("Content-Disposition", "attachment; filename=x-ui.db")
|
c.Header("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
|
||||||
// Write the file contents to the response
|
// Write the file contents to the response
|
||||||
c.Writer.Write(db)
|
c.Writer.Write(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidFilename(filename string) bool {
|
||||||
|
// Validate that the filename only contains allowed characters
|
||||||
|
return filenameRegex.MatchString(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) importDB(c *gin.Context) {
|
||||||
|
// Get the file from the request body
|
||||||
|
file, _, err := c.Request.FormFile("db")
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error reading db file", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
// Always restart Xray before return
|
||||||
|
defer a.serverService.RestartXrayService()
|
||||||
|
defer func() {
|
||||||
|
a.lastGetStatusTime = time.Now()
|
||||||
|
}()
|
||||||
|
// Import it
|
||||||
|
err = a.serverService.ImportDB(file)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, "Import DB", nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||||
cert, err := a.serverService.GetNewX25519Cert()
|
cert, err := a.serverService.GetNewX25519Cert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
allSetting, err := a.settingService.GetAllSetting()
|
allSetting, err := a.settingService.GetAllSetting()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
@@ -58,7 +58,7 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
@@ -67,29 +67,74 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
|||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
expireDiff, err := a.settingService.GetExpireDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
trafficDiff, err := a.settingService.GetTrafficDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
defaultCert, err := a.settingService.GetCertFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
defaultKey, err := a.settingService.GetKeyFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tgBotEnable, err := a.settingService.GetTgbotenabled()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subEnable, err := a.settingService.GetSubEnable()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subPort, err := a.settingService.GetSubPort()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subPath, err := a.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subDomain, err := a.settingService.GetSubDomain()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subKeyFile, err := a.settingService.GetSubKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subCertFile, err := a.settingService.GetSubCertFile()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subTLS := false
|
||||||
|
if subKeyFile != "" || subCertFile != "" {
|
||||||
|
subTLS = true
|
||||||
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"expireDiff": expireDiff,
|
"expireDiff": expireDiff,
|
||||||
"trafficDiff": trafficDiff,
|
"trafficDiff": trafficDiff,
|
||||||
"defaultCert": defaultCert,
|
"defaultCert": defaultCert,
|
||||||
"defaultKey": defaultKey,
|
"defaultKey": defaultKey,
|
||||||
|
"tgBotEnable": tgBotEnable,
|
||||||
|
"subEnable": subEnable,
|
||||||
|
"subPort": subPort,
|
||||||
|
"subPath": subPath,
|
||||||
|
"subDomain": subDomain,
|
||||||
|
"subTLS": subTLS,
|
||||||
}
|
}
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
@@ -98,27 +143,27 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
|||||||
allSetting := &entity.AllSetting{}
|
allSetting := &entity.AllSetting{}
|
||||||
err := c.ShouldBind(allSetting)
|
err := c.ShouldBind(allSetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.settingService.UpdateAllSetting(allSetting)
|
err = a.settingService.UpdateAllSetting(allSetting)
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) updateUser(c *gin.Context) {
|
func (a *SettingController) updateUser(c *gin.Context) {
|
||||||
form := &updateUserForm{}
|
form := &updateUserForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), errors.New(I18n(c, "pages.setting.toasts.originalUserPassIncorrect")))
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.NewUsername == "" || form.NewPassword == "" {
|
if form.NewUsername == "" || form.NewPassword == "" {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), errors.New(I18n(c, "pages.setting.toasts.userPassMustBeNotEmpty")))
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.userPassMustBeNotEmpty")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
||||||
@@ -127,19 +172,19 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
|||||||
user.Password = form.NewPassword
|
user.Password = form.NewPassword
|
||||||
session.SetLoginUser(c, user)
|
session.SetLoginUser(c, user)
|
||||||
}
|
}
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) restartPanel(c *gin.Context) {
|
func (a *SettingController) restartPanel(c *gin.Context) {
|
||||||
err := a.panelService.RestartPanel(time.Second * 3)
|
err := a.panelService.RestartPanel(time.Second * 3)
|
||||||
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.restartPanel"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) updateSecret(c *gin.Context) {
|
func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
form := &updateSecretForm{}
|
form := &updateSecretForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifySetting"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||||
@@ -147,8 +192,9 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
|||||||
user.LoginSecret = form.LoginSecret
|
user.LoginSecret = form.LoginSecret
|
||||||
session.SetLoginUser(c, user)
|
session.SetLoginUser(c, user)
|
||||||
}
|
}
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.modifyUser"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
loginUser := session.GetLoginUser(c)
|
loginUser := session.GetLoginUser(c)
|
||||||
user := a.userService.GetUserSecret(loginUser.Id)
|
user := a.userService.GetUserSecret(loginUser.Id)
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
m.Success = true
|
m.Success = true
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
m.Msg = msg + I18n(c, "success")
|
m.Msg = msg + I18nWeb(c, "success")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.Success = false
|
m.Success = false
|
||||||
m.Msg = msg + I18n(c, "fail") + ": " + err.Error()
|
m.Msg = msg + I18nWeb(c, "fail") + ": " + err.Error()
|
||||||
logger.Warning(msg+I18n(c, "fail")+": ", err)
|
logger.Warning(msg+I18nWeb(c, "fail")+": ", err)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ func NewXUIController(g *gin.RouterGroup) *XUIController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/xui")
|
g = g.Group("/panel")
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
|
|
||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.GET("/inbounds", a.inbounds)
|
g.GET("/inbounds", a.inbounds)
|
||||||
g.GET("/setting", a.setting)
|
g.GET("/settings", a.settings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
@@ -37,6 +37,6 @@ func (a *XUIController) inbounds(c *gin.Context) {
|
|||||||
html(c, "inbounds.html", "pages.inbounds.title", nil)
|
html(c, "inbounds.html", "pages.inbounds.title", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) setting(c *gin.Context) {
|
func (a *XUIController) settings(c *gin.Context) {
|
||||||
html(c, "setting.html", "pages.setting.title", nil)
|
html(c, "settings.html", "pages.settings.title", nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,18 @@ type AllSetting struct {
|
|||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
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"`
|
||||||
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -54,10 +63,25 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubListen != "" {
|
||||||
|
ip := net.ParseIP(s.SubListen)
|
||||||
|
if ip == nil {
|
||||||
|
return common.NewError("Sub listen is not valid ip:", s.SubListen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebPort <= 0 || s.WebPort > 65535 {
|
if s.WebPort <= 0 || s.WebPort > 65535 {
|
||||||
return common.NewError("web port is not a valid port:", s.WebPort)
|
return common.NewError("web port is not a valid port:", s.WebPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubPort <= 0 || s.SubPort > 65535 {
|
||||||
|
return common.NewError("Sub port is not a valid port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.SubPort == s.WebPort {
|
||||||
|
return common.NewError("Sub and Web could not use same port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
||||||
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,6 +89,13 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubCertFile != "" || s.SubKeyFile != "" {
|
||||||
|
_, err := tls.LoadX509KeyPair(s.SubCertFile, s.SubKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("cert file <%v> or key file <%v> invalid: %v", s.SubCertFile, s.SubKeyFile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(s.WebBasePath, "/") {
|
if !strings.HasPrefix(s.WebBasePath, "/") {
|
||||||
s.WebBasePath = "/" + s.WebBasePath
|
s.WebBasePath = "/" + s.WebBasePath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var webServer WebServer
|
var webServer WebServer
|
||||||
|
var subServer SubServer
|
||||||
|
|
||||||
type WebServer interface {
|
type WebServer interface {
|
||||||
GetCron() *cron.Cron
|
GetCron() *cron.Cron
|
||||||
GetCtx() context.Context
|
GetCtx() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SubServer interface {
|
||||||
|
GetCtx() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
func SetWebServer(s WebServer) {
|
func SetWebServer(s WebServer) {
|
||||||
webServer = s
|
webServer = s
|
||||||
}
|
}
|
||||||
@@ -21,3 +26,11 @@ func SetWebServer(s WebServer) {
|
|||||||
func GetWebServer() WebServer {
|
func GetWebServer() WebServer {
|
||||||
return webServer
|
return webServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSubServer(s SubServer) {
|
||||||
|
subServer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubServer() SubServer {
|
||||||
|
return subServer
|
||||||
|
}
|
||||||
|
|||||||
82
web/global/hashStorage.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HashEntry struct {
|
||||||
|
Hash string
|
||||||
|
Value string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashStorage struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]HashEntry
|
||||||
|
Expiration time.Duration
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHashStorage(expiration time.Duration) *HashStorage {
|
||||||
|
return &HashStorage{
|
||||||
|
Data: make(map[string]HashEntry),
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) SaveHash(query string) string {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
md5Hash := md5.Sum([]byte(query))
|
||||||
|
md5HashString := hex.EncodeToString(md5Hash[:])
|
||||||
|
|
||||||
|
entry := HashEntry{
|
||||||
|
Hash: md5HashString,
|
||||||
|
Value: query,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Data[md5HashString] = entry
|
||||||
|
|
||||||
|
return md5HashString
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (h *HashStorage) GetValue(hash string) (string, bool) {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
|
||||||
|
entry, exists := h.Data[hash]
|
||||||
|
|
||||||
|
return entry.Value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) IsMD5(hash string) bool {
|
||||||
|
match, _ := regexp.MatchString("^[a-f0-9]{32}$", hash)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) RemoveExpiredHashes() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for hash, entry := range h.Data {
|
||||||
|
if now.Sub(entry.Timestamp) > h.Expiration {
|
||||||
|
delete(h.Data, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) Reset() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
h.Data = make(map[string]HashEntry)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "promptModal"}}
|
{{define "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"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
@@ -36,12 +36,12 @@
|
|||||||
},
|
},
|
||||||
confirm() {},
|
confirm() {},
|
||||||
open({
|
open({
|
||||||
title='',
|
title = '',
|
||||||
type='text',
|
type = 'text',
|
||||||
value='',
|
value = '',
|
||||||
okText='{{ i18n "sure"}}',
|
okText = '{{ i18n "sure"}}',
|
||||||
confirm=() => {},
|
confirm = () => {},
|
||||||
}) {
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
|||||||
@@ -1,46 +1,60 @@
|
|||||||
{{define "qrcodeModal"}}
|
{{define "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"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
|
</a-tag>
|
||||||
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
|
<a-divider>Subscription</a-divider>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
|
||||||
|
</template>
|
||||||
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
|
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
clientIndex: 0,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
copyText: '',
|
client: null,
|
||||||
qrcode: null,
|
qrcodes: [],
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title='', content='', dbInbound=new DBInbound(), copyText='') {
|
subId: '',
|
||||||
|
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.clientIndex = clientIndex;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
if (ObjectUtil.isEmpty(copyText)) {
|
settings = JSON.parse(this.inbound.settings);
|
||||||
this.copyText = content;
|
this.client = settings.clients[clientIndex];
|
||||||
|
remark = this.dbInbound.remark + "-" + this.client.email;
|
||||||
|
address = this.dbInbound.address;
|
||||||
|
this.subId = '';
|
||||||
|
this.qrcodes = [];
|
||||||
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
this.qrcodes.push({
|
||||||
|
remark: remark + "-" + domain.remark,
|
||||||
|
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, clientIndex)
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.copyText = copyText;
|
this.qrcodes.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, clientIndex)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
qrModalApp.$nextTick(() => {
|
|
||||||
if (this.qrcode === null) {
|
|
||||||
this.qrcode = new QRious({
|
|
||||||
element: document.querySelector('#qrCode'),
|
|
||||||
size: 260,
|
|
||||||
value: content,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.qrcode.value = content;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -48,21 +62,46 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const qrModalApp = new Vue({
|
const qrModalApp = new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
el: '#qrcode-modal',
|
el: '#qrcode-modal',
|
||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard() {
|
copyToClipboard(elmentId,content) {
|
||||||
this.qrModal.clipboard = new ClipboardJS('#qrCode', {
|
this.qrModal.clipboard = new ClipboardJS('#'+elmentId, {
|
||||||
text: () => this.qrModal.copyText,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.qrModal.clipboard.on('success', () => {
|
this.qrModal.clipboard.on('success', () => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
this.qrModal.clipboard.destroy();
|
this.qrModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
setQrCode(elmentId,content) {
|
||||||
|
new QRious({
|
||||||
|
element: document.querySelector('#'+elmentId),
|
||||||
|
size: 260,
|
||||||
|
value: content,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
protocol = app.subSettings.tls ? "https://" : "http://";
|
||||||
|
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
||||||
|
subPort = app.subSettings.port;
|
||||||
|
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
||||||
|
subPath = app.subSettings.path;
|
||||||
|
return protocol + hostName + port + subPath + subID;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
if (qrModal.client.subId){
|
||||||
|
qrModal.subId = qrModal.client.subId;
|
||||||
|
this.setQrCode("qrCode-sub",this.genSubLink(qrModal.subId));
|
||||||
|
}
|
||||||
|
qrModal.qrcodes.forEach((element,index) => {
|
||||||
|
this.setQrCode("qrCode-"+index, element.link);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
|
:download="txtModal.fileName">
|
||||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
{{ i18n "download" }} [[ txtModal.fileName ]]
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-input type="textarea" v-model="txtModal.content"
|
<a-input type="textarea" v-model="txtModal.content"
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
qrcode: null,
|
qrcode: null,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title='', content='', fileName='') {
|
show: function (title = '', content = '', fileName = '') {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-input-group-addon {
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
width: 50px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-input-affix-wrapper .ant-input-prefix {
|
.ant-input-affix-wrapper .ant-input-prefix {
|
||||||
left: 23px;
|
left: 23px;
|
||||||
}
|
}
|
||||||
@@ -26,20 +32,26 @@
|
|||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectLang{
|
.centered {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak class="login" :class="themeSwitcher.darkCardClass">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
||||||
<h1>{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
@@ -48,35 +60,33 @@
|
|||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/>
|
<a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input type="password" v-model.trim="user.password"
|
<password-input icon="lock" v-model.trim="user.password"
|
||||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||||
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
|
</password-input>
|
||||||
</a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="secretEnable">
|
<a-form-item v-if="secretEnable">
|
||||||
<a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login">
|
<password-input icon="key" v-model.trim="user.loginSecret"
|
||||||
<a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/>
|
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
||||||
|
</password-input>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
|
<a-row justify="center" class="centered">
|
||||||
|
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||||
|
:style="loading ? { width: '50px' } : { display: 'block', width: '100%' }">
|
||||||
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
|
</a-button>
|
||||||
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
<a-row justify="center" class="centered">
|
||||||
<a-row justify="center" class="selectLang">
|
<a-col :span="12">
|
||||||
<a-col :span="5"><span>Language :</span></a-col>
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
<a-col :span="7">
|
|
||||||
<a-select
|
|
||||||
ref="selectLang"
|
|
||||||
v-model="lang"
|
|
||||||
@change="setLang(lang)"
|
|
||||||
>
|
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs" >
|
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
@@ -84,6 +94,11 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-row justify="center" class="centered">
|
||||||
|
<theme-switch />
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -91,24 +106,24 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
|
{{template "component/password" .}}
|
||||||
<script>
|
<script>
|
||||||
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
|
||||||
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
|
||||||
const deg = RandomUtil.randomIntRange(0, 360);
|
|
||||||
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
|
||||||
document.querySelector('#app').style.background = background;
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
|
themeSwitcher,
|
||||||
loading: false,
|
loading: false,
|
||||||
user: new User(),
|
user: new User(),
|
||||||
secretEnable: false,
|
secretEnable: false,
|
||||||
lang : ""
|
lang: ""
|
||||||
},
|
},
|
||||||
created(){
|
async created() {
|
||||||
this.lang = getLang();
|
this.updateBackground();
|
||||||
this.secretEnable = this.getSecretStatus();
|
this.lang = getLang();
|
||||||
|
this.secretEnable = await this.getSecretStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async login() {
|
async login() {
|
||||||
@@ -116,20 +131,33 @@
|
|||||||
const msg = await HttpUtil.post('/login', this.user);
|
const msg = await HttpUtil.post('/login', this.user);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
location.href = basePath + 'xui/';
|
location.href = basePath + 'panel/';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getSecretStatus() {
|
async getSecretStatus() {
|
||||||
this.loading= true;
|
this.loading = true;
|
||||||
const msg = await HttpUtil.post('/getSecretStatus');
|
const msg = await HttpUtil.post('/getSecretStatus');
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (msg.success){
|
if (msg.success) {
|
||||||
this.secretEnable = msg.obj;
|
this.secretEnable = msg.obj;
|
||||||
return msg.obj;
|
return msg.obj;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
updateBackground() {
|
||||||
|
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
||||||
|
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
||||||
|
const deg = RandomUtil.randomIntRange(0, 360);
|
||||||
|
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
||||||
|
document.querySelector('#app').style.background = this.themeSwitcher.isDarkTheme ? colors.dark.bg : background;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'themeSwitcher.isDarkTheme'(newVal, oldVal) {
|
||||||
|
this.updateBackground();
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<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"
|
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
@@ -33,6 +33,30 @@
|
|||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="app.subSettings.enable">
|
||||||
|
<span slot="label">
|
||||||
|
Subscription
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="app.tgBotEnable">
|
||||||
|
<span slot="label">
|
||||||
|
Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
@@ -43,29 +67,24 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="clientsBulkModal.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Subscription">
|
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Telegram ID">
|
|
||||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
@@ -75,15 +94,17 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
||||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
@@ -92,7 +113,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -122,37 +143,42 @@
|
|||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
clients = [];
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method = clientsBulkModal.emailMethod;
|
||||||
if(method>1){
|
if (method > 1) {
|
||||||
start=clientsBulkModal.firstNum;
|
start = clientsBulkModal.firstNum;
|
||||||
end=clientsBulkModal.lastNum + 1;
|
end = clientsBulkModal.lastNum + 1;
|
||||||
} else {
|
} else {
|
||||||
start=0;
|
start = 0;
|
||||||
end=clientsBulkModal.quantity;
|
end = clientsBulkModal.quantity;
|
||||||
}
|
}
|
||||||
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
|
prefix = (method > 0 && clientsBulkModal.emailPrefix.length > 0) ? clientsBulkModal.emailPrefix : "";
|
||||||
useNum=(method>1);
|
useNum = (method > 1);
|
||||||
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : "";
|
postfix = (method > 2 && clientsBulkModal.emailPostfix.length > 0) ? clientsBulkModal.emailPostfix : "";
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
||||||
if(method==4) newClient.email = "";
|
if (method == 4) newClient.email = "";
|
||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
newClient.subId = clientsBulkModal.subId;
|
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
|
||||||
newClient.tgId = clientsBulkModal.tgId;
|
if (clientsBulkModal.tgId.length > 0) newClient.tgId = clientsBulkModal.tgId;
|
||||||
newClient.limitIp = clientsBulkModal.limitIp;
|
newClient.limitIp = clientsBulkModal.limitIp;
|
||||||
newClient._totalGB = clientsBulkModal.totalGB;
|
newClient._totalGB = clientsBulkModal.totalGB;
|
||||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
if (clientsBulkModal.inbound.canEnableTlsFlow()) {
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
if(clientsBulkModal.inbound.xtls){
|
if (clientsBulkModal.inbound.xtls) {
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
clients.push(newClient);
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
show({
|
||||||
|
title = '',
|
||||||
|
okText = '{{ i18n "sure" }}',
|
||||||
|
dbInbound = null,
|
||||||
|
confirm = (inbound, dbInbound) => { }
|
||||||
|
}) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
@@ -160,24 +186,25 @@
|
|||||||
this.quantity = 1;
|
this.quantity = 1;
|
||||||
this.totalGB = 0;
|
this.totalGB = 0;
|
||||||
this.expiryTime = 0;
|
this.expiryTime = 0;
|
||||||
this.emailMethod= 0;
|
this.emailMethod = 0;
|
||||||
this.limitIp= 0;
|
this.limitIp = 0;
|
||||||
this.firstNum= 1;
|
this.firstNum = 1;
|
||||||
this.lastNum= 1;
|
this.lastNum = 1;
|
||||||
this.emailPrefix= "";
|
this.emailPrefix = "";
|
||||||
this.emailPostfix= "";
|
this.emailPostfix = "";
|
||||||
this.subId= "";
|
this.subId = "";
|
||||||
this.tgId= "";
|
this.tgId = "";
|
||||||
this.flow= "";
|
this.flow = "";
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -186,6 +213,7 @@
|
|||||||
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
||||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
||||||
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks();
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -209,10 +237,11 @@
|
|||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
|
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
|
||||||
},
|
},
|
||||||
set delayedExpireDays(days){
|
set delayedExpireDays(days) {
|
||||||
this.clientsBulkModal.expiryTime = -86400000 * days;
|
this.clientsBulkModal.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "clientsModal"}}
|
{{define "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="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -20,16 +20,15 @@
|
|||||||
oldClientId: "",
|
oldClientId: "",
|
||||||
index: null,
|
index: null,
|
||||||
clientIps: null,
|
clientIps: null,
|
||||||
isExpired: false,
|
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
if(clientModal.isEdit){
|
if (clientModal.isEdit) {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||||
} else {
|
} else {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
@@ -38,13 +37,12 @@
|
|||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
if (isEdit){
|
if (isEdit) {
|
||||||
if (this.clients[index].expiryTime < 0){
|
if (this.clients[index].expiryTime < 0) {
|
||||||
this.delayedStart = true;
|
this.delayedStart = true;
|
||||||
}
|
}
|
||||||
this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id;
|
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
} else {
|
} else {
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
}
|
}
|
||||||
@@ -52,18 +50,27 @@
|
|||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientId(protocol, client) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.TROJAN: return client.password;
|
||||||
|
case Protocols.SHADOWSOCKS: return client.email;
|
||||||
|
default: return client.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
addClient(protocol, clients) {
|
addClient(protocol, clients) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
|
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -94,39 +101,27 @@
|
|||||||
return this.clientModal.isEdit;
|
return this.clientModal.isEdit;
|
||||||
},
|
},
|
||||||
get isTrafficExhausted() {
|
get isTrafficExhausted() {
|
||||||
if(!clientStats) return false
|
if (!clientStats) return false
|
||||||
if(clientStats.total <= 0) return false
|
if (clientStats.total <= 0) return false
|
||||||
if(clientStats.up + clientStats.down < clientStats.total) return false
|
if (clientStats.up + clientStats.down < clientStats.total) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.clientModal.isExpired
|
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
||||||
},
|
},
|
||||||
get statsColor() {
|
get statsColor() {
|
||||||
if(!clientStats) return 'blue'
|
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
||||||
if(clientStats.total <= 0) return 'blue'
|
|
||||||
else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan'
|
|
||||||
else return 'red'
|
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
},
|
},
|
||||||
set delayedExpireDays(days){
|
set delayedExpireDays(days) {
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getNewEmail(client) {
|
async getDBClientIps(email, event) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
const msg = await HttpUtil.post('/panel/inbound/clientIps/' + email);
|
||||||
var string = '';
|
|
||||||
var len = 6 + Math.floor(Math.random() * 5);
|
|
||||||
for(var ii=0; ii<len; ii++){
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
client.email = string;
|
|
||||||
},
|
|
||||||
async getDBClientIps(email,event) {
|
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email);
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -140,22 +135,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
|
const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.getElementById("clientIPs").value = ""
|
document.getElementById("clientIPs").value = ""
|
||||||
},
|
},
|
||||||
resetClientTraffic(email,dbInboundId,iconElement) {
|
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
iconElement.disabled = true;
|
iconElement.disabled = true;
|
||||||
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email);
|
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.clientModal.clientStats.up = 0;
|
this.clientModal.clientStats.up = 0;
|
||||||
this.clientModal.clientStats.down = 0;
|
this.clientModal.clientStats.down = 0;
|
||||||
@@ -166,5 +161,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{{define "menuItems"}}
|
{{define "menuItems"}}
|
||||||
<a-menu-item key="{{ .base_path }}xui/">
|
<a-menu-item key="{{ .base_path }}panel/">
|
||||||
<a-icon type="dashboard"></a-icon>
|
<a-icon type="dashboard"></a-icon>
|
||||||
<span>{{ i18n "menu.dashboard"}}</span>
|
<span>{{ i18n "menu.dashboard"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}xui/inbounds">
|
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user"></a-icon>
|
||||||
<span>{{ i18n "menu.inbounds"}}</span>
|
<span>{{ i18n "menu.inbounds"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}xui/setting">
|
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.setting"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>Client</span>-->
|
<!-- <span>Client</span>-->
|
||||||
<!--</a-menu-item>-->
|
<!--</a-menu-item>-->
|
||||||
@@ -23,17 +23,14 @@
|
|||||||
|
|
||||||
|
|
||||||
{{define "commonSider"}}
|
{{define "commonSider"}}
|
||||||
<a-layout-sider :theme="siderDrawer.theme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bg-colors"></a-icon>
|
||||||
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
<theme-switch />
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="siderDrawer.changeTheme()"></a-switch>
|
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@@ -41,32 +38,25 @@
|
|||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible"
|
:visible="siderDrawer.visible"
|
||||||
:wrap-class-name="siderDrawer.isDarkTheme ? 'ant-drawer-dark' : ''"
|
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
||||||
:wrap-style="{ padding: 0 }">
|
:wrap-style="{ padding: 0 }">
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bg-colors"></a-icon>
|
||||||
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
<theme-switch />
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="siderDrawer.changeTheme()"></a-switch>
|
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||||
{{template "menuItems" .}}
|
{{template "menuItems" .}}
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
<script>
|
<script>
|
||||||
const darkClass = "ant-card-dark";
|
|
||||||
const bgDarkStyle = "background-color: #242c3a";
|
|
||||||
const siderDrawer = {
|
const siderDrawer = {
|
||||||
visible: false,
|
visible: false,
|
||||||
collapsed: false,
|
|
||||||
isDarkTheme: localStorage.getItem("dark-mode") === 'false' ? false : true,
|
|
||||||
show() {
|
show() {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
@@ -76,17 +66,6 @@
|
|||||||
change() {
|
change() {
|
||||||
this.visible = !this.visible;
|
this.visible = !this.visible;
|
||||||
},
|
},
|
||||||
toggleCollapsed() {
|
|
||||||
this.collapsed = !this.collapsed;
|
|
||||||
},
|
|
||||||
changeTheme() {
|
|
||||||
this.isDarkTheme = ! this.isDarkTheme;
|
|
||||||
localStorage.setItem("dark-mode", this.isDarkTheme);
|
|
||||||
},
|
|
||||||
get theme() {
|
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
35
web/html/xui/component/password.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{{define "component/passwordInput"}}
|
||||||
|
<template>
|
||||||
|
<a-input :value="value" :type="showPassword ? 'text' : 'password'"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
@input="$emit('input', $event.target.value)">
|
||||||
|
<template v-if="icon" #prefix>
|
||||||
|
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||||
|
</template>
|
||||||
|
<template #addonAfter>
|
||||||
|
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||||
|
@click="toggleShowPassword"
|
||||||
|
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/password"}}
|
||||||
|
<script>
|
||||||
|
Vue.component('password-input', {
|
||||||
|
props: ["title", "value", "placeholder", "icon"],
|
||||||
|
template: `{{template "component/passwordInput"}}`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPassword: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleShowPassword() {
|
||||||
|
this.showPassword = !this.showPassword;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
{{define "component/settingListItem"}}
|
{{define "component/settingListItem"}}
|
||||||
<a-list-item style="padding: 20px">
|
<a-list-item style="padding: 20px">
|
||||||
<a-row>
|
<a-row v-if="type === 'textarea'">
|
||||||
|
<a-col>
|
||||||
|
<a-list-item-meta :title="title" :description="desc"/>
|
||||||
|
<a-textarea class="ant-setting-textarea" :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10 }"></a-textarea>
|
||||||
|
<!--a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 30 }"></a-textarea-->
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row v-else>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta :title="title" :description="desc"/>
|
<a-list-item-meta :title="title" :description="desc"/>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -9,10 +16,7 @@
|
|||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)" :min="min"></a-input>
|
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
||||||
</template>
|
|
||||||
<template v-else-if="type === 'textarea'">
|
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'switch'">
|
<template v-else-if="type === 'switch'">
|
||||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||||
@@ -29,4 +33,4 @@
|
|||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
58
web/html/xui/component/themeSwitch.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{{define "component/themeSwitchTemplate"}}
|
||||||
|
<template>
|
||||||
|
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
||||||
|
checked-children="☀"
|
||||||
|
un-checked-children="🌙"
|
||||||
|
@change="themeSwitcher.toggleTheme()">
|
||||||
|
</a-switch>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/themeSwitcher"}}
|
||||||
|
<script>
|
||||||
|
const colors = {
|
||||||
|
dark: {
|
||||||
|
bg: "#242c3a",
|
||||||
|
text: "hsla(0,0%,100%,.65)"
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
bg: '#f0f2f5',
|
||||||
|
text: "rgba(0, 0, 0, 0.7)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createThemeSwitcher() {
|
||||||
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
|
return {
|
||||||
|
isDarkTheme,
|
||||||
|
bgStyle: `background: ${colors[theme].bg};`,
|
||||||
|
textStyle: `color: ${colors[theme].text};`,
|
||||||
|
darkClass: isDarkTheme ? 'ant-dark' : '',
|
||||||
|
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
||||||
|
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
||||||
|
get currentTheme() {
|
||||||
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
|
},
|
||||||
|
toggleTheme() {
|
||||||
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
|
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
||||||
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
|
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
||||||
|
this.textStyle = `color: ${colors[this.theme].text};`;
|
||||||
|
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
||||||
|
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
||||||
|
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeSwitcher = createThemeSwitcher();
|
||||||
|
|
||||||
|
Vue.component('theme-switch', {
|
||||||
|
props: [],
|
||||||
|
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||||
|
data: () => ({ themeSwitcher }),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -1,36 +1,62 @@
|
|||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form layout="inline" v-if="client">
|
<a-form layout="inline" v-if="client">
|
||||||
<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="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
|
Account is (Expired|Traffic Ended) And Disabled
|
||||||
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 150px;" ></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="{{ i18n "pages.inbounds.enable" }}">
|
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN">
|
<br>
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;" ></a-input>
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
||||||
|
<a-input-number v-model="client.alterId"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<span slot="label">
|
||||||
|
Subscription
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable" >
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<span slot="label">
|
||||||
</a-form-item>
|
Telegram ID
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -43,7 +69,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -64,25 +90,29 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-form layout="block">
|
<a-form layout="block">
|
||||||
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 2, maxRows: 10 }">
|
<a-textarea id="clientIPs" readonly
|
||||||
|
@click="getDBClientIps(client.email,$event)"
|
||||||
|
placeholder="Click To Get IPs"
|
||||||
|
:auto-size="{ minRows: 2, maxRows: 10 }">
|
||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
@@ -90,9 +120,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input-number v-model="client._totalGB":min="0" style="width: 70px;"></a-input-number>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<template v-if="isEdit && clientStats">
|
<template v-if="isEdit && clientStats">
|
||||||
<span>{{ i18n "usage" }}:</span>
|
<br>
|
||||||
|
<span> {{ i18n "usage" }}:</span>
|
||||||
<a-tag :color="statsColor">
|
<a-tag :color="statsColor">
|
||||||
[[ sizeFormat(clientStats.up) ]] /
|
[[ sizeFormat(clientStats.up) ]] /
|
||||||
[[ sizeFormat(clientStats.down) ]]
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
@@ -100,19 +131,22 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||||
|
v-if="client.email.length > 0"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
|
||||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
@@ -121,7 +155,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -24,12 +24,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="inbound.listen"></a-input>
|
<a-input v-model.trim="inbound.listen"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input type="number" v-model.number="inbound.port"></a-input>
|
<a-input-number v-model="inbound.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
@@ -41,7 +43,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
@@ -50,8 +52,8 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,17 @@
|
|||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||||
<a-input type="number" v-model.number="inbound.settings.port"></a-input>
|
<a-input-number v-model="inbound.settings.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="FollowRedirect">
|
<a-form-item label="FollowRedirect">
|
||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,7 +1,113 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Password">
|
||||||
|
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.password" style="width: 250px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
|
<span slot="label">
|
||||||
|
Subscription
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
|
<span slot="label">
|
||||||
|
Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-else>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-collapse v-else>
|
||||||
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
||||||
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -9,7 +115,7 @@
|
|||||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<!-- <a-form-item label="Password authentication">-->
|
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<a-form-item label='{{ i18n "username" }}'>
|
<a-form-item label='{{ i18n "username" }}'>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
||||||
@@ -13,11 +13,11 @@
|
|||||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.settings.udp"
|
<a-form-item v-if="inbound.settings.udp" label="IP">
|
||||||
label="IP">
|
|
||||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -1,31 +1,49 @@
|
|||||||
{{define "form/trojan"}}
|
{{define "form/trojan"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="client.email" style="width: 150px;" ></a-input>
|
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<a-form-item label="Password">
|
||||||
<a-form-item label="Password">
|
<a-icon @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<span slot="label">
|
||||||
</a-form-item>
|
Subscription
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
<a-form-item>
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
|
<span slot="label">
|
||||||
|
Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -35,60 +53,69 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<br>
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
</a-select>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-form-item>
|
</a-select>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<span slot="label">
|
<a-form-item>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span slot="label">
|
||||||
<a-tooltip>
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<template slot="title">
|
<a-tooltip>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<template slot="title">
|
||||||
</template>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</template>
|
||||||
</a-tooltip>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</span>
|
</a-tooltip>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
</span>
|
||||||
</a-form-item>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<span slot="label">
|
<br>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-tooltip>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
<template slot="title">
|
</a-form-item>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<br>
|
||||||
</template>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-tooltip>
|
</a-form-item>
|
||||||
</span>
|
<a-form-item v-else>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<span slot="label">
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
</a-collapse-panel>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</a-collapse>
|
</template>
|
||||||
<a-collapse v-else>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
</a-tooltip>
|
||||||
<table width="100%">
|
</span>
|
||||||
<tr class="client-table-header">
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
</tr>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
</a-form-item>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-collapse-panel>
|
||||||
</tr>
|
</a-collapse>
|
||||||
</table>
|
<a-collapse v-else>
|
||||||
</a-collapse-panel>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
</a-collapse>
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<template v-if="inbound.isTcp">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button type="primary" size="small"
|
<a-button type="primary" size="small" @click="inbound.settings.addTrojanFallback()">
|
||||||
@click="inbound.settings.addTrojanFallback()">
|
|
||||||
+
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -115,7 +142,7 @@
|
|||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xVer">
|
<a-form-item label="xVer">
|
||||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
<a-input-number v-model="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -1,106 +1,133 @@
|
|||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="client.email" style="width: 150px;" ></a-input>
|
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<a-form-item label="ID">
|
||||||
<a-form-item label="ID">
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;" ></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<span slot="label">
|
||||||
</a-form-item>
|
Subscription
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
<a-form-item>
|
</template>
|
||||||
<span slot="label">
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
</span>
|
||||||
<template slot="title">
|
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
</a-tooltip>
|
<span slot="label">
|
||||||
</span>
|
Telegram ID
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
</template>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
</a-tooltip>
|
||||||
</a-select>
|
</span>
|
||||||
</a-form-item>
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
</a-form-item>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-form-item>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<span slot="label">
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
</a-select>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
<a-form-item>
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
<span slot="label">
|
</template>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-tooltip>
|
</a-tooltip>
|
||||||
<template slot="title">
|
</span>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<br>
|
||||||
</a-tooltip>
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
</span>
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
</a-form-item>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-form-item>
|
</a-select>
|
||||||
<span slot="label">
|
</a-form-item>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||||
<a-tooltip>
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<template slot="title">
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</template>
|
</a-select>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</a-form-item>
|
||||||
</a-tooltip>
|
<a-form-item>
|
||||||
</span>
|
<span slot="label">
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<a-tooltip>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<template slot="title">
|
||||||
</a-form-item>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
</a-collapse-panel>
|
</template>
|
||||||
</a-collapse>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-collapse v-else>
|
</a-tooltip>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
</span>
|
||||||
<table width="100%">
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<tr class="client-table-header">
|
</a-form-item>
|
||||||
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
<br>
|
||||||
</tr>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-form-item>
|
||||||
</tr>
|
<br>
|
||||||
</table>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
</a-collapse-panel>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-collapse>
|
</a-form-item>
|
||||||
|
<a-form-item v-else>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-collapse v-else>
|
||||||
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<template v-if="inbound.isTcp">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button type="primary" size="small"
|
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">
|
||||||
@click="inbound.settings.addFallback()">
|
|
||||||
+
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||||
<a-divider>
|
<a-divider>
|
||||||
@@ -121,7 +148,7 @@
|
|||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xVer">
|
<a-form-item label="xVer">
|
||||||
<a-input type="number" v-model.number="fallback.xver"></a-input>
|
<a-input-number v-model="fallback.xver"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -1,34 +1,54 @@
|
|||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<br>
|
||||||
<a-form-item label="ID">
|
<a-form-item label='{{ i18n "additional" }} ID'>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input-number v-model="client.alterId"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
<br>
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<a-form-item label="ID">
|
||||||
</a-form-item>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<span slot="label">
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
Subscription
|
||||||
</a-form-item>
|
<a-tooltip>
|
||||||
<a-form-item>
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
|
<span slot="label">
|
||||||
|
Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -38,52 +58,61 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;"></a-input>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<br>
|
||||||
<span slot="label">
|
<a-form-item>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span slot="label">
|
||||||
<a-tooltip>
|
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<template slot="title">
|
<a-tooltip>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<template slot="title">
|
||||||
</template>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</template>
|
||||||
</a-tooltip>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</span>
|
</a-tooltip>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
</span>
|
||||||
</a-form-item>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<span slot="label">
|
<br>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-tooltip>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
<template slot="title">
|
</a-form-item>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<br>
|
||||||
</template>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-tooltip>
|
</a-form-item>
|
||||||
</span>
|
<a-form-item v-else>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<span slot="label">
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
</a-collapse-panel>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</a-collapse>
|
</template>
|
||||||
<a-collapse v-else>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
|
</a-tooltip>
|
||||||
<table width="100%">
|
</span>
|
||||||
<tr class="client-table-header">
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
</tr>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
</a-form-item>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-collapse-panel>
|
||||||
</tr>
|
</a-collapse>
|
||||||
</table>
|
<a-collapse v-else>
|
||||||
</a-collapse-panel>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
||||||
</a-collapse>
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Sniffing
|
Sniffing
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,38 +1,46 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="none">None(Not Camouflage)</a-select-option>
|
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP(Camouflage Video Call)</a-select-option>
|
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
||||||
<a-select-option value="utp">UTP(Camouflage BT Download)</a-select-option>
|
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
||||||
<a-select-option value="wechat-video">Wechat-Video(Camouflage WeChat Video)</a-select-option>
|
<a-select-option value="wechat-video">Wechat-Video (Camouflage WeChat Video)</a-select-option>
|
||||||
<a-select-option value="dtls">DTLS(Camouflage DTLS 1.2 Packages)</a-select-option>
|
<a-select-option value="dtls">DTLS (Camouflage DTLS 1.2 Packages)</a-select-option>
|
||||||
<a-select-option value="wireguard">Wireguard(Camouflage Wireguard Packages)</a-select-option>
|
<a-select-option value="wireguard">Wireguard (Camouflage Wireguard Packages)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.number="inbound.stream.kcp.seed"></a-input>
|
<a-input v-model="inbound.stream.kcp.seed"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="MTU">
|
<a-form-item label="MTU">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="TTI (ms)">
|
<a-form-item label="TTI (ms)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.tti"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Uplink Capacity (MB/S)">
|
<a-form-item label="Uplink Capacity (MB/S)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.upCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Downlink Capacity (MB/S)">
|
<a-form-item label="Downlink Capacity (MB/S)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.downCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Congestion">
|
<a-form-item label="Congestion">
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Read Buffer Size (MB)">
|
<a-form-item label="Read Buffer Size (MB)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Write Buffer Size (MB)">
|
<a-form-item label="Write Buffer Size (MB)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="none">none(not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp(camouflage video call)</a-select-option>
|
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp(camouflage BT download)</a-select-option>
|
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
|
||||||
<a-select-option value="wechat-video">wechat-video(camouflage WeChat video)</a-select-option>
|
<a-select-option value="wechat-video">wechat-video (camouflage WeChat video)</a-select-option>
|
||||||
<a-select-option value="dtls">dtls(camouflage DTLS 1.2 packages)</a-select-option>
|
<a-select-option value="dtls">dtls (camouflage DTLS 1.2 packages)</a-select-option>
|
||||||
<a-select-option value="wireguard">wireguard(camouflage wireguard packages)</a-select-option>
|
<a-select-option value="wireguard">wireguard (camouflage wireguard packages)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">KCP</a-select-option>
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WS</a-select-option>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="HTTP {{ i18n "camouflage" }}">
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
<a-switch
|
<a-switch
|
||||||
:checked="inbound.stream.tcp.type === 'http'"
|
:checked="inbound.stream.tcp.type === 'http'"
|
||||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
@@ -13,8 +13,7 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp request -->
|
<!-- tcp request -->
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||||
layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
||||||
<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>
|
||||||
@@ -26,12 +25,11 @@
|
|||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<br>
|
||||||
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small"
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">+</a-button>
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -39,19 +37,16 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small"
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp response -->
|
<!-- tcp response -->
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||||
layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
||||||
<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>
|
||||||
@@ -61,12 +56,10 @@
|
|||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small"
|
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
||||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -74,10 +67,7 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small"
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -8,12 +8,11 @@
|
|||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<br>
|
||||||
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small"
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
|
||||||
+
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -21,10 +20,7 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small"
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||||
@click="inbound.stream.ws.removeHeader(index)">
|
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
Reality
|
Reality
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.Realitydec" }}</span>
|
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
XTLS
|
XTLS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.XTLSdec" }}</span>
|
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -33,22 +33,40 @@
|
|||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='Multi Domain'>
|
||||||
|
<a-switch v-model="multiDomain"></a-switch>
|
||||||
|
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="multiDomain">
|
||||||
|
<a-row>
|
||||||
|
<span>Domains:</span>
|
||||||
|
<a-button v-if="multiDomain" type="primary" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})" style="margin-left: 10px">+</a-button>
|
||||||
|
</a-row>
|
||||||
|
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
||||||
|
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
||||||
|
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-else label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CipherSuites">
|
<a-form-item label="CipherSuites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="">auto</a-select-option>
|
<a-select-option value="">auto</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MinVersion">
|
<a-form-item label="MinVersion">
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MaxVersion">
|
<a-form-item label="MaxVersion">
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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-form-item>
|
</a-form-item>
|
||||||
@@ -57,7 +75,7 @@
|
|||||||
</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"
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
style="width: 170px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
style="width: 170px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -70,36 +88,43 @@
|
|||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
</a-form-item>
|
</a-radio-group>
|
||||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<template v-if="cert.useFile">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
|
<a-input v-model.trim="cert.certFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
|
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- xtls settings -->
|
<!-- xtls settings -->
|
||||||
<a-form v-if="inbound.xtls" layout="inline">
|
<a-form v-else-if="inbound.xtls" layout="inline">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.settings.serverName" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label="Alpn">
|
||||||
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
|
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
|
||||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
@@ -108,28 +133,32 @@
|
|||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<template v-for="cert,index in inbound.stream.xtls.certs">
|
||||||
<a-radio-group v-model="inbound.stream.xtls.certs[0].useFile" button-style="solid">
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
</a-form-item>
|
</a-radio-group>
|
||||||
<template v-if="inbound.stream.xtls.certs[0].useFile">
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small" @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
<a-input v-model.trim="inbound.stream.xtls.certs[0].certFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].key"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<template v-if="cert.useFile">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
|
<a-input v-model.trim="cert.certFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
|
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
@@ -140,25 +169,30 @@
|
|||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xVer">
|
<a-form-item label="xVer">
|
||||||
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
<a-input-number v-model="inbound.stream.reality.xver" :min="0" style="width: 60px"></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"
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
style="width: 135px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
style="width: 135px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="dest">
|
<a-form-item label="Dest">
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Server Names">
|
<a-form-item label="Server Names">
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ShortIds">
|
<a-form-item label="ShortIds">
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width: 150px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label="SpiderX">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Private Key">
|
<a-form-item label="Private Key">
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
||||||
@@ -166,7 +200,7 @@
|
|||||||
<a-form-item label="Public Key">
|
<a-form-item label="Public Key">
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item >
|
<a-form-item>
|
||||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|||||||
@@ -29,20 +29,21 @@
|
|||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
<a-tag :color="statsColor(record, client.email)">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
||||||
<template v-if="client._totalGB > 0">
|
<template v-if="client._totalGB > 0">
|
||||||
<a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]]GB</a-tag>
|
<a-tag :color="statsColor(record, client.email)">[[client._totalGB]]GB</a-tag>
|
||||||
<a-tag v-else color="cyan">[[client._totalGB]]GB</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime > 0">
|
||||||
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
|
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
<a-tag v-else-if="client.expiryTime < 0" color="cyan">
|
||||||
|
[[ client._expiryTime ]] {{ i18n "pages.client.days" }}
|
||||||
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -3,62 +3,65 @@
|
|||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr><td>
|
<tr>
|
||||||
<table>
|
<td>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
<table>
|
||||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
||||||
</table>
|
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
||||||
</td>
|
</table>
|
||||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
</td>
|
||||||
<table>
|
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<tr>
|
<table>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<tr>
|
||||||
</tr>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
</tr>
|
||||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
|
||||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
|
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
||||||
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
|
||||||
</template>
|
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
||||||
|
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
<template v-if="inbound.isQuic">
|
</template>
|
||||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
|
||||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
<template v-if="inbound.isQuic">
|
||||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||||
</template>
|
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||||
|
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
||||||
<template v-if="inbound.isKcp">
|
</template>
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
|
||||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
<template v-if="inbound.isKcp">
|
||||||
</template>
|
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||||
|
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||||
<template v-if="inbound.isGrpc">
|
</template>
|
||||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
|
||||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
<template v-if="inbound.isGrpc">
|
||||||
</template>
|
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||||
</table>
|
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</td></tr>
|
</template>
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
</table>
|
||||||
<td v-if="inbound.tls">
|
</td>
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
</tr>
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
</td>
|
<td v-if="inbound.tls">
|
||||||
<td v-else-if="inbound.xtls">
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
</td>
|
||||||
</td>
|
<td v-else-if="inbound.xtls">
|
||||||
<td v-else-if="inbound.reality">
|
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
</td>
|
||||||
</td>
|
<td v-else-if="inbound.reality">
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
|
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||||
|
</td>
|
||||||
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -85,7 +88,7 @@
|
|||||||
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats" color="green">
|
||||||
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
||||||
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
||||||
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
||||||
@@ -97,7 +100,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||||
<a-tag :color="infoModal.isExpired ? 'red' : 'blue'">
|
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -106,16 +109,16 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<tr v-if="infoModal.clientSettings.subId">
|
<a-divider>Subscription link</a-divider>
|
||||||
<td>Subscription link</td>
|
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
|
||||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
<a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-icon>
|
||||||
</tr>
|
</template>
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<td>Telegram Username</td>
|
<a-divider>Telegram Username</a-divider>
|
||||||
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
<a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a>
|
||||||
</tr>
|
<a-icon id="copy-tg-link" type="snippets" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)"></a-icon>
|
||||||
</table>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-divider></a-divider>
|
<a-divider></a-divider>
|
||||||
@@ -124,7 +127,8 @@
|
|||||||
<th>{{ i18n "encryption" }}</th>
|
<th>{{ i18n "encryption" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
</tr><tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
@@ -136,7 +140,8 @@
|
|||||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
<th>FollowRedirect</th>
|
<th>FollowRedirect</th>
|
||||||
</tr><tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
@@ -149,15 +154,18 @@
|
|||||||
<th>{{ i18n "password" }} Auth</th>
|
<th>{{ i18n "password" }} Auth</th>
|
||||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
</tr><tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
</tr><tr v-if="inbound.settings.auth == 'password'">
|
</tr>
|
||||||
|
<tr v-if="inbound.settings.auth == 'password'">
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td>{{ i18n "username" }}</td>
|
<td>{{ i18n "username" }}</td>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr>
|
||||||
|
<tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
@@ -169,7 +177,8 @@
|
|||||||
<th> </th>
|
<th> </th>
|
||||||
<th>{{ i18n "username" }}</th>
|
<th>{{ i18n "username" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr>
|
||||||
|
<tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
@@ -179,11 +188,18 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
<div v-if="dbInbound.hasLink()">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<p>[[ infoModal.link ]]</p>
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
<a-col :span="21"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :span="3" style="text-align: right;">
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>{{ i18n "copy" }}
|
||||||
|
</button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const infoModal = {
|
const infoModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
@@ -194,31 +210,56 @@
|
|||||||
upStats: 0,
|
upStats: 0,
|
||||||
downStats: 0,
|
downStats: 0,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
link: null,
|
links: [],
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
|
subLink: '',
|
||||||
|
tgLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.link = dbInbound.genLink(index);
|
|
||||||
this.settings = JSON.parse(this.inbound.settings);
|
this.settings = JSON.parse(this.inbound.settings);
|
||||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.visible = true;
|
remark = this.dbInbound.remark + "-" + this.clientSettings.email;
|
||||||
infoModalApp.$nextTick(() => {
|
address = this.dbInbound.address;
|
||||||
if (this.clipboard === null) {
|
this.links = [];
|
||||||
this.clipboard = new ClipboardJS('#copy-url-link', {
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
text: () => this.link,
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
this.links.push({
|
||||||
|
remark: remark + "-" + domain.remark,
|
||||||
|
link: this.inbound.genLink(domain.domain, remark + "-" + domain.remark, index)
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
});
|
||||||
|
} else {
|
||||||
|
this.links.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.clientSettings) {
|
||||||
|
if (this.clientSettings.subId) {
|
||||||
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
}
|
}
|
||||||
});
|
if (this.clientSettings.tgId) {
|
||||||
|
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
protocol = app.subSettings.tls ? "https://" : "http://";
|
||||||
|
hostName = app.subSettings.domain === "" ? window.location.hostname : app.subSettings.domain;
|
||||||
|
subPort = app.subSettings.port;
|
||||||
|
port = (subPort === 443 && app.subSettings.tls) || (subPort === 80 && !app.subSettings.tls) ? "" : ":" + String(subPort);
|
||||||
|
subPath = app.subSettings.path;
|
||||||
|
return protocol + hostName + port + subPath + subID;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const infoModalApp = new Vue({
|
const infoModalApp = new Vue({
|
||||||
@@ -233,42 +274,32 @@
|
|||||||
return this.infoModal.inbound;
|
return this.infoModal.inbound;
|
||||||
},
|
},
|
||||||
get isActive() {
|
get isActive() {
|
||||||
if(infoModal.clientStats){
|
if (infoModal.clientStats) {
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isEnable() {
|
||||||
if(infoModal.clientSettings){
|
if (infoModal.clientSettings) {
|
||||||
return infoModal.clientSettings.enable;
|
return infoModal.clientSettings.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
},
|
},
|
||||||
get subBase() {
|
|
||||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
|
||||||
},
|
|
||||||
get tgBase() {
|
|
||||||
return "https://t.me/"
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyTextToClipboard(elmentId,content) {
|
copyToClipboard(elmentId, content) {
|
||||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.infoModal.clipboard.on('success', () => {
|
this.infoModal.clipboard.on('success', () => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
this.infoModal.clipboard.destroy();
|
this.infoModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
statsColor(stats) {
|
statsColor(stats) {
|
||||||
if(!stats) return 'blue'
|
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||||
if(stats['total'] === 0) return 'blue'
|
|
||||||
else if(stats['total'] > 0 && (stats['down']+stats['up']) < stats['total']) return 'cyan'
|
|
||||||
else return 'red'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
|
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) {
|
show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => {}, isEdit = false }) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
if (inbound) {
|
if (inbound) {
|
||||||
@@ -44,10 +44,11 @@
|
|||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -86,9 +87,21 @@
|
|||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
},
|
},
|
||||||
set delayedExpireDays(days){
|
set delayedExpireDays(days) {
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
|
get multiDomain() {
|
||||||
|
return this.inbound.stream.tls.settings.domains.length > 0;
|
||||||
|
},
|
||||||
|
set multiDomain(value) {
|
||||||
|
if (value) {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [{remark: "", domain: window.location.host.split(":")[0]}];
|
||||||
|
} else {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
@@ -99,11 +112,15 @@
|
|||||||
this.inModal.inbound.reality = false;
|
this.inModal.inbound.reality = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDefaultCertData(){
|
setDefaultCertData(index) {
|
||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
async getNewX25519Cert(){
|
setDefaultCertXtls(index) {
|
||||||
|
inModal.inbound.stream.xtls.certs[index].certFile = app.defaultCert;
|
||||||
|
inModal.inbound.stream.xtls.certs[index].keyFile = app.defaultKey;
|
||||||
|
},
|
||||||
|
async getNewX25519Cert() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
@@ -112,15 +129,6 @@
|
|||||||
}
|
}
|
||||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
},
|
|
||||||
getNewEmail(client) {
|
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
|
||||||
var string = '';
|
|
||||||
var len = 6 + Math.floor(Math.random() * 5);
|
|
||||||
for(var ii=0; ii<len; ii++){
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
client.email = string;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,10 +12,11 @@
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
@@ -41,19 +42,19 @@
|
|||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -64,14 +65,14 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
@@ -93,17 +94,28 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
||||||
<a-select v-model="refreshInterval"
|
<a-select v-model="refreshInterval"
|
||||||
|
style="width: 65px;"
|
||||||
v-if="isRefreshEnabled"
|
v-if="isRefreshEnabled"
|
||||||
@change="changeRefreshInterval"
|
@change="changeRefreshInterval"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-icon type="sync" :spin="isRefreshEnabled"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-switch v-model="enableFilter"
|
||||||
|
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
||||||
|
@change="toggleFilter" style="margin-right: 10px;">
|
||||||
|
</a-switch>
|
||||||
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||||
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||||
|
<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-group>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
@@ -114,16 +126,12 @@
|
|||||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
|
||||||
<a-icon type="qrcode"></a-icon>
|
|
||||||
{{ i18n "qrCode" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="edit">
|
<a-menu-item key="edit">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -155,7 +163,7 @@
|
|||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="clone">
|
<a-menu-item key="clone">
|
||||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.Clone"}}
|
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delete">
|
<a-menu-item key="delete">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
@@ -177,19 +185,19 @@
|
|||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<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>
|
<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>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</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-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>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -230,7 +238,7 @@
|
|||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-table
|
<a-table
|
||||||
v-else-if="record.protocol === Protocols.TROJAN"
|
v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS"
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerTrojanColumns"
|
:columns="innerTrojanColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
@@ -247,6 +255,7 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
@@ -263,7 +272,7 @@
|
|||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 40,
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -298,7 +307,7 @@
|
|||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
@@ -307,11 +316,11 @@
|
|||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'Password', width: 120, dataIndex: "password" },
|
{ title: 'Password', width: 170, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -319,10 +328,13 @@
|
|||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
siderDrawer,
|
||||||
|
themeSwitcher,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
inbounds: [],
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
enableFilter: false,
|
||||||
|
filterBy: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
expireDiff: 0,
|
expireDiff: 0,
|
||||||
trafficDiff: 0,
|
trafficDiff: 0,
|
||||||
@@ -330,28 +342,52 @@
|
|||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: {},
|
clientCount: {},
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
|
subSettings: {
|
||||||
|
enable : false,
|
||||||
|
port: 0,
|
||||||
|
path: '',
|
||||||
|
domain: '',
|
||||||
|
tls: false
|
||||||
|
},
|
||||||
|
tgBotEnable: false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning=true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
this.refreshing = true;
|
||||||
|
const msg = await HttpUtil.post('/panel/inbound/list');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshing = false;
|
||||||
|
}, 500);
|
||||||
},
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.expireDiff = msg.obj.expireDiff * 86400000;
|
with(msg.obj){
|
||||||
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
|
this.expireDiff = expireDiff * 86400000;
|
||||||
this.defaultCert = msg.obj.defaultCert;
|
this.trafficDiff = trafficDiff * 1073741824;
|
||||||
this.defaultKey = msg.obj.defaultKey;
|
this.defaultCert = defaultCert;
|
||||||
|
this.defaultKey = defaultKey;
|
||||||
|
this.tgBotEnable = tgBotEnable;
|
||||||
|
this.subSettings = {
|
||||||
|
enable : subEnable,
|
||||||
|
port: subPort,
|
||||||
|
path: subPath,
|
||||||
|
domain: subDomain,
|
||||||
|
tls: subTLS
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
@@ -361,29 +397,33 @@
|
|||||||
to_inbound = dbInbound.toInbound()
|
to_inbound = dbInbound.toInbound()
|
||||||
this.inbounds.push(to_inbound);
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)) {
|
||||||
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.searchInbounds(this.searchKey);
|
if(this.enableFilter){
|
||||||
|
this.filterInbounds();
|
||||||
|
} else {
|
||||||
|
this.searchInbounds(this.searchKey);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound,inbound){
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
if(clients){
|
if (clients) {
|
||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if(dbInbound.enable){
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if(!client.enable) {
|
if (!client.enable) {
|
||||||
depleted.push(client.email);
|
depleted.push(client.email);
|
||||||
} else {
|
} else {
|
||||||
if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) ||
|
if ((client.expiryTime > 0 && (client.expiryTime - now < this.expireDiff)) ||
|
||||||
(client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email);
|
(client.total > 0 && (client.total - (client.up + client.down) < this.trafficDiff))) expiring.push(client.email);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -409,10 +449,10 @@
|
|||||||
if (ObjectUtil.deepSearch(inbound, key)) {
|
if (ObjectUtil.deepSearch(inbound, key)) {
|
||||||
const newInbound = new DBInbound(inbound);
|
const newInbound = new DBInbound(inbound);
|
||||||
const inboundSettings = JSON.parse(inbound.settings);
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
if (inboundSettings.hasOwnProperty('clients')){
|
if (inboundSettings.hasOwnProperty('clients')) {
|
||||||
const searchedSettings = { "clients": [] };
|
const searchedSettings = { "clients": [] };
|
||||||
inboundSettings.clients.forEach(client => {
|
inboundSettings.clients.forEach(client => {
|
||||||
if (ObjectUtil.deepSearch(client, key)){
|
if (ObjectUtil.deepSearch(client, key)) {
|
||||||
searchedSettings.clients.push(client);
|
searchedSettings.clients.push(client);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -423,7 +463,39 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
generalActions(action){
|
filterInbounds() {
|
||||||
|
if (ObjectUtil.isEmpty(this.filterBy)) {
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
} else {
|
||||||
|
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||||
|
this.dbInbounds.forEach(inbound => {
|
||||||
|
const newInbound = new DBInbound(inbound);
|
||||||
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
|
if (this.clientCount[inbound.id] && this.clientCount[inbound.id].hasOwnProperty(this.filterBy)){
|
||||||
|
const list = this.clientCount[inbound.id][this.filterBy];
|
||||||
|
if (list.length > 0) {
|
||||||
|
const filteredSettings = { "clients": [] };
|
||||||
|
inboundSettings.clients.forEach(client => {
|
||||||
|
if (list.includes(client.email)) {
|
||||||
|
filteredSettings.clients.push(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
||||||
|
this.searchedInbounds.push(newInbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFilter(){
|
||||||
|
if(this.enableFilter) {
|
||||||
|
this.searchKey = '';
|
||||||
|
} else {
|
||||||
|
this.filterBy = '';
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
@@ -476,9 +548,9 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openCloneInbound(dbInbound) {
|
openCloneInbound(dbInbound) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}}' + dbInbound.remark,
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
@@ -491,7 +563,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
async cloneInbound(baseInbound, dbInbound) {
|
async cloneInbound(baseInbound, dbInbound) {
|
||||||
const inbound = new Inbound();
|
|
||||||
const data = {
|
const data = {
|
||||||
up: dbInbound.up,
|
up: dbInbound.up,
|
||||||
down: dbInbound.down,
|
down: dbInbound.down,
|
||||||
@@ -500,19 +571,19 @@
|
|||||||
enable: dbInbound.enable,
|
enable: dbInbound.enable,
|
||||||
expiryTime: dbInbound.expiryTime,
|
expiryTime: dbInbound.expiryTime,
|
||||||
|
|
||||||
listen: inbound.listen,
|
listen: '',
|
||||||
port: inbound.port,
|
port: RandomUtil.randomIntRange(10000, 60000),
|
||||||
protocol: baseInbound.protocol,
|
protocol: baseInbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
openAddInbound() {
|
openAddInbound() {
|
||||||
inModal.show({
|
inModal.show({
|
||||||
title: '{{ i18n "pages.inbounds.addInbound"}}',
|
title: '{{ i18n "pages.inbounds.addInbound"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.addTo"}}',
|
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (inbound, dbInbound) => {
|
||||||
inModal.loading();
|
inModal.loading();
|
||||||
@@ -527,7 +598,7 @@
|
|||||||
const inbound = dbInbound.toInbound();
|
const inbound = dbInbound.toInbound();
|
||||||
inModal.show({
|
inModal.show({
|
||||||
title: '{{ i18n "pages.inbounds.modifyInbound"}}',
|
title: '{{ i18n "pages.inbounds.modifyInbound"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.revise"}}',
|
okText: '{{ i18n "pages.inbounds.update"}}',
|
||||||
cancelText: '{{ i18n "close" }}',
|
cancelText: '{{ i18n "close" }}',
|
||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
@@ -556,7 +627,7 @@
|
|||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
async updateInbound(inbound, dbInbound) {
|
async updateInbound(inbound, dbInbound) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -575,7 +646,7 @@
|
|||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
openAddClient(dbInboundId) {
|
openAddClient(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -621,30 +692,30 @@
|
|||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
findIndexOfClient(clients,client) {
|
findIndexOfClient(clients, client) {
|
||||||
firstKey = Object.keys(client)[0];
|
firstKey = Object.keys(client)[0];
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() +']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/addClient`, data);
|
await this.submit(`/panel/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() +']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
@@ -659,42 +730,70 @@
|
|||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId,client) {
|
delClient(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = dbInbound.protocol == "trojan" ? client.password : client.id;
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
switch(protocol){
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientId(protocol, client) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.TROJAN: return client.password;
|
||||||
|
case Protocols.SHADOWSOCKS: return client.email;
|
||||||
|
default: return client.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkFallback(dbInbound) {
|
||||||
|
newDbInbound = new DBInbound(dbInbound);
|
||||||
|
if (dbInbound.listen.startsWith("@")){
|
||||||
|
rootInbound = this.inbounds.find((i) =>
|
||||||
|
i.tls &&
|
||||||
|
['trojan','vless'].includes(i.protocol) &&
|
||||||
|
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
||||||
|
);
|
||||||
|
if (rootInbound) {
|
||||||
|
newDbInbound.listen = rootInbound.listen;
|
||||||
|
newDbInbound.port = rootInbound.port;
|
||||||
|
newInbound = newDbInbound.toInbound();
|
||||||
|
newInbound.stream.security = 'tls';
|
||||||
|
newInbound.stream.tls = rootInbound.stream.tls;
|
||||||
|
newDbInbound.streamSettings = newInbound.stream.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newDbInbound;
|
||||||
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const link = dbInbound.genLink(clientIndex);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, clientIndex);
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInbound, index) {
|
||||||
infoModal.show(dbInbound, index);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
|
infoModal.show(newDbInbound, index);
|
||||||
},
|
},
|
||||||
switchEnable(dbInboundId) {
|
switchEnable(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
|
||||||
},
|
},
|
||||||
async switchEnableClient(dbInboundId, client) {
|
async switchEnableClient(dbInboundId, client) {
|
||||||
this.loading()
|
this.loading()
|
||||||
@@ -703,8 +802,8 @@
|
|||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id;
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
await this.updateClient(clients[index],dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data) {
|
||||||
@@ -714,98 +813,101 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
getInboundClients(dbInbound) {
|
||||||
if(dbInbound.protocol == Protocols.VLESS) {
|
if (dbInbound.protocol == Protocols.VLESS) {
|
||||||
return dbInbound.toInbound().settings.vlesses
|
return dbInbound.toInbound().settings.vlesses;
|
||||||
} else if(dbInbound.protocol == Protocols.VMESS) {
|
} else if (dbInbound.protocol == Protocols.VMESS) {
|
||||||
return dbInbound.toInbound().settings.vmesses
|
return dbInbound.toInbound().settings.vmesses;
|
||||||
} else if(dbInbound.protocol == Protocols.TROJAN) {
|
} else if (dbInbound.protocol == Protocols.TROJAN) {
|
||||||
return dbInbound.toInbound().settings.trojans
|
return dbInbound.toInbound().settings.trojans;
|
||||||
|
} else if (dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
||||||
|
return dbInbound.toInbound().settings.shadowsockses;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetClientTraffic(client,dbInboundId) {
|
resetClientTraffic(client, dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email),
|
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetAllClientTraffics(dbInboundId) {
|
resetAllClientTraffics(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
delDepletedClients(dbInboundId) {
|
delDepletedClients(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index)
|
||||||
},
|
},
|
||||||
getUpStats(dbInbound, email) {
|
getUpStats(dbInbound, email) {
|
||||||
if(email.length == 0) return 0
|
if (email.length == 0) return 0
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||||
return clientStats ? clientStats.up : 0
|
return clientStats ? clientStats.up : 0
|
||||||
},
|
},
|
||||||
getDownStats(dbInbound, email) {
|
getDownStats(dbInbound, email) {
|
||||||
if(email.length == 0) return 0
|
if (email.length == 0) return 0
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||||
return clientStats ? clientStats.down : 0
|
return clientStats ? clientStats.down : 0
|
||||||
},
|
},
|
||||||
isTrafficExhausted(dbInbound, email) {
|
statsColor(dbInbound, email) {
|
||||||
if(email.length == 0) return false
|
if(email.length == 0) return 'blue';
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.down + clientStats.up > clientStats.total : false
|
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
||||||
return clientStats ? clientStats['enable'] : true
|
return clientStats ? clientStats['enable'] : true
|
||||||
},
|
},
|
||||||
isRemovable(dbInbound_id){
|
isRemovable(dbInbound_id) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',dbInbound.genInboundLinks,dbInbound.remark);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
|
||||||
},
|
},
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = '';
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText += dbInbound.genInboundLinks
|
copyText += dbInbound.genInboundLinks
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
||||||
},
|
},
|
||||||
async startDataRefreshLoop() {
|
async startDataRefreshLoop() {
|
||||||
while (this.isRefreshEnabled) {
|
while (this.isRefreshEnabled) {
|
||||||
try {
|
try {
|
||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(this.refreshInterval);
|
await PromiseUtil.sleep(this.refreshInterval);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleRefresh() {
|
toggleRefresh() {
|
||||||
@@ -814,9 +916,16 @@
|
|||||||
this.startDataRefreshLoop();
|
this.startDataRefreshLoop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeRefreshInterval(){
|
changeRefreshInterval() {
|
||||||
localStorage.setItem("refreshInterval", this.refreshInterval);
|
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||||
},
|
},
|
||||||
|
async manualRefresh() {
|
||||||
|
if (!this.refreshing) {
|
||||||
|
this.spinning = true;
|
||||||
|
await this.getDBInbounds();
|
||||||
|
this.spinning = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey: debounce(function (newVal) {
|
searchKey: debounce(function (newVal) {
|
||||||
@@ -859,6 +968,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
@@ -868,5 +978,6 @@
|
|||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,32 +13,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark h2 {
|
.ant-card-dark h2 {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0, 0%, 100%, .65);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak>
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU</div>
|
<div>CPU</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
@@ -75,14 +76,14 @@
|
|||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
<a-tooltip v-if="status.xray.state === State.Error">
|
||||||
@@ -109,20 +110,20 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
@@ -159,7 +160,7 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
@@ -188,9 +189,10 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
footer="">
|
footer="">
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||||
@@ -201,9 +203,10 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="themeSwitcher.darkCardClass"
|
||||||
width="800px"
|
width="800px"
|
||||||
footer="">
|
footer="">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
@@ -211,7 +214,7 @@
|
|||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs(logModal.rows)"
|
@change="openLogs(logModal.rows)"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
@@ -227,12 +230,31 @@
|
|||||||
{{ i18n "download" }} x-ui.log
|
{{ i18n "download" }} x-ui.log
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-input type="textarea" v-model="logModal.logs" disabled="true"
|
<a-input type="textarea" v-model="logModal.logs" disabled="true"
|
||||||
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
|
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
|
:closable="true" :class="themeSwitcher.darkCardClass"
|
||||||
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
|
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
||||||
|
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
||||||
|
[[ backupModal.description ]]
|
||||||
|
</p>
|
||||||
|
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||||
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
|
[[ backupModal.exportText ]]
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" @click="importDatabase()">
|
||||||
|
[[ backupModal.importText ]]
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "textModal"}}
|
{{template "textModal"}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@@ -275,13 +297,13 @@
|
|||||||
this.disk = new CurTotal(0, 0);
|
this.disk = new CurTotal(0, 0);
|
||||||
this.loads = [0, 0, 0];
|
this.loads = [0, 0, 0];
|
||||||
this.mem = new CurTotal(0, 0);
|
this.mem = new CurTotal(0, 0);
|
||||||
this.netIO = {up: 0, down: 0};
|
this.netIO = { up: 0, down: 0 };
|
||||||
this.netTraffic = {sent: 0, recv: 0};
|
this.netTraffic = { sent: 0, recv: 0 };
|
||||||
this.swap = new CurTotal(0, 0);
|
this.swap = new CurTotal(0, 0);
|
||||||
this.tcpCount = 0;
|
this.tcpCount = 0;
|
||||||
this.udpCount = 0;
|
this.udpCount = 0;
|
||||||
this.uptime = 0;
|
this.uptime = 0;
|
||||||
this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
|
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
@@ -339,14 +361,39 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const backupModal = {
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
exportText: '',
|
||||||
|
importText: '',
|
||||||
|
show({
|
||||||
|
title = '{{ i18n "pages.index.backupTitle" }}',
|
||||||
|
description = '{{ i18n "pages.index.backupDescription" }}',
|
||||||
|
exportText = '{{ i18n "pages.index.exportDatabase" }}',
|
||||||
|
importText = '{{ i18n "pages.index.importDatabase" }}',
|
||||||
|
}) {
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.exportText = exportText;
|
||||||
|
this.importText = importText;
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
siderDrawer,
|
||||||
|
themeSwitcher,
|
||||||
status: new Status(),
|
status: new Status(),
|
||||||
versionModal,
|
versionModal,
|
||||||
logModal,
|
logModal,
|
||||||
|
backupModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
},
|
},
|
||||||
@@ -378,17 +425,16 @@
|
|||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: themeSwitcher.darkCardClass,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefreshh"}}');
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
await HttpUtil.post(`/server/installXray/${version}`);
|
await HttpUtil.post(`/server/installXray/${version}`);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
//here add stop xray function
|
|
||||||
async stopXrayService() {
|
async stopXrayService() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/stopXrayService');
|
const msg = await HttpUtil.post('server/stopXrayService');
|
||||||
@@ -397,7 +443,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//here add restart xray function
|
|
||||||
async restartXrayService() {
|
async restartXrayService() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/restartXrayService');
|
const msg = await HttpUtil.post('server/restartXrayService');
|
||||||
@@ -406,27 +451,67 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(rows){
|
async openLogs(rows) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/logs/'+rows);
|
const msg = await HttpUtil.post('server/logs/' + rows);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj,rows);
|
logModal.show(msg.obj, rows);
|
||||||
},
|
},
|
||||||
async openConfig(){
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.post('server/getConfigJson');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
|
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
||||||
},
|
},
|
||||||
getBackup(){
|
openBackup() {
|
||||||
|
backupModal.show({
|
||||||
|
title: '{{ i18n "pages.index.backupTitle" }}',
|
||||||
|
description: '{{ i18n "pages.index.backupDescription" }}',
|
||||||
|
exportText: '{{ i18n "pages.index.exportDatabase" }}',
|
||||||
|
importText: '{{ i18n "pages.index.importDatabase" }}',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportDatabase() {
|
||||||
window.location = basePath + 'server/getDb';
|
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() {
|
async mounted() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -1,773 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
{{template "head" .}}
|
|
||||||
<style>
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
.ant-layout-content {
|
|
||||||
margin: 24px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tabs-bar {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-list-item {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(.ant-card-dark)>.ant-tabs-top-bar {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<a-layout id="app" v-cloak>
|
|
||||||
{{ template "commonSider" . }}
|
|
||||||
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
|
||||||
<a-layout-content>
|
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
|
||||||
<a-space direction="vertical">
|
|
||||||
<a-space direction="horizontal">
|
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
|
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
|
|
||||||
<a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
|
||||||
<a-tab-pane key="1" tab='{{ i18n "pages.setting.panelConfig"}}'>
|
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelListeningIP"}}' desc='{{ i18n "pages.setting.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.panelPort"}}' desc='{{ i18n "pages.setting.panelPortDesc"}}' v-model.number="allSetting.webPort"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.sessionMaxAge" }}' desc='{{ i18n "pages.setting.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
|
||||||
<a-list-item>
|
|
||||||
<a-row style="padding: 20px">
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title="Language" />
|
|
||||||
</a-col>
|
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-select
|
|
||||||
ref="selectLang"
|
|
||||||
v-model="lang"
|
|
||||||
@change="setLang(lang)"
|
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span> <span v-text="l.name"></span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
|
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
|
|
||||||
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
|
||||||
<a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
|
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.setting.currentPassword"}}'>
|
|
||||||
<a-input type="password" v-model="user.oldPassword" style="max-width: 300px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.setting.newUsername"}}'>
|
|
||||||
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.setting.newPassword"}}'>
|
|
||||||
<a-input type="password" v-model="user.newPassword" style="max-width: 300px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
|
||||||
<a-list-item style="padding: 20px">
|
|
||||||
<a-row>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.setting.loginSecurity" }}' description='{{ i18n "pages.setting.loginSecurityDesc" }}'/>
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<template>
|
|
||||||
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-list-item style="padding: 20px">
|
|
||||||
<a-row>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<a-list-item-meta title='{{ i18n "pages.setting.secretToken" }}' description='{{ i18n "pages.setting.secretTokenDesc" }}'/>
|
|
||||||
|
|
||||||
</a-col>
|
|
||||||
<a-col :lg="24" :xl="12">
|
|
||||||
<svg
|
|
||||||
@click="getNewSecret"
|
|
||||||
xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/>
|
|
||||||
</svg>
|
|
||||||
<template>
|
|
||||||
<a-textarea type="text" id='token' :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
|
||||||
</template>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-list-item>
|
|
||||||
<a-button type="primary" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
|
||||||
</a-form>
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
|
||||||
<a-divider>{{ i18n "pages.setting.actions"}}</a-divider>
|
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
|
|
||||||
</a-space>
|
|
||||||
|
|
||||||
<a-divider>{{ i18n "pages.setting.basicTemplate"}}</a-divider>
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.generalConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
|
||||||
{{ i18n "pages.setting.generalConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigAds"}}' desc='{{ i18n "pages.setting.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPorn"}}' desc='{{ i18n "pages.setting.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.countryConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
|
||||||
{{ i18n "pages.setting.countryConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRDomain"}}' desc='{{ i18n "pages.setting.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaIp"}}' desc='{{ i18n "pages.setting.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.ipv4Configs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
|
||||||
{{ i18n "pages.setting.ipv4ConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.warpConfigs"}}'>
|
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
|
||||||
{{ i18n "pages.setting.warpConfigsDesc" }}
|
|
||||||
</h2>
|
|
||||||
</a-row>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.setting.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
|
|
||||||
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
|
||||||
<a-collapse>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigInbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigOutbounds"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigRoutings"}}'>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
|
|
||||||
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
|
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.telegramBotEnable" }}' desc='{{ i18n "pages.setting.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramToken"}}' desc='{{ i18n "pages.setting.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramChatId"}}' desc='{{ i18n "pages.setting.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.telegramNotifyTime"}}' desc='{{ i18n "pages.setting.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.tgNotifyBackup" }}' desc='{{ i18n "pages.setting.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.tgNotifyCpu" }}' desc='{{ i18n "pages.setting.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
|
|
||||||
<a-tab-pane key="5" tab='{{ i18n "pages.setting.otherSetting"}}'>
|
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.timeZonee"}}' desc='{{ i18n "pages.setting.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
|
||||||
</a-list>
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</a-space>
|
|
||||||
</a-spin>
|
|
||||||
</a-layout-content>
|
|
||||||
</a-layout>
|
|
||||||
</a-layout>
|
|
||||||
{{template "js" .}}
|
|
||||||
{{template "component/setting"}}
|
|
||||||
<script>
|
|
||||||
|
|
||||||
const app = new Vue({
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
el: '#app',
|
|
||||||
data: {
|
|
||||||
siderDrawer,
|
|
||||||
spinning: false,
|
|
||||||
oldAllSetting: new AllSetting(),
|
|
||||||
allSetting: new AllSetting(),
|
|
||||||
saveBtnDisable: true,
|
|
||||||
user: new User(),
|
|
||||||
lang: getLang(),
|
|
||||||
ipv4Settings: {
|
|
||||||
tag: "IPv4",
|
|
||||||
protocol: "freedom",
|
|
||||||
settings: {
|
|
||||||
domainStrategy: "UseIPv4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
warpSettings: {
|
|
||||||
tag: "WARP",
|
|
||||||
protocol: "socks",
|
|
||||||
settings: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
address: "127.0.0.1",
|
|
||||||
port: 40000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
settingsData: {
|
|
||||||
protocols: {
|
|
||||||
bittorrent: ["bittorrent"],
|
|
||||||
},
|
|
||||||
ips: {
|
|
||||||
local: ["geoip:private"],
|
|
||||||
google: ["geoip:google"],
|
|
||||||
cn: ["geoip:cn"],
|
|
||||||
ir: ["geoip:ir"],
|
|
||||||
ru: ["geoip:ru"],
|
|
||||||
},
|
|
||||||
domains: {
|
|
||||||
ads: [
|
|
||||||
"geosite:category-ads-all",
|
|
||||||
"geosite:category-ads",
|
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
],
|
|
||||||
porn: ["geosite:category-porn"],
|
|
||||||
openai: ["geosite:openai"],
|
|
||||||
google: ["geosite:google"],
|
|
||||||
spotify: ["geosite:spotify"],
|
|
||||||
netflix: ["geosite:netflix"],
|
|
||||||
cn: ["geosite:cn"],
|
|
||||||
ru: ["geosite:category-ru-gov"],
|
|
||||||
ir: [
|
|
||||||
"regexp:.*\\.ir$",
|
|
||||||
"ext:iran.dat:ir",
|
|
||||||
"ext:iran.dat:other",
|
|
||||||
"ext:iran.dat:ads",
|
|
||||||
"geosite:category-ir"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loading(spinning = true , obj) {
|
|
||||||
if(obj == null)
|
|
||||||
this.spinning = spinning;
|
|
||||||
},
|
|
||||||
async getAllSetting() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/xui/setting/all");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.oldAllSetting = new AllSetting(msg.obj);
|
|
||||||
this.allSetting = new AllSetting(msg.obj);
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
await this.getUserSecret();
|
|
||||||
},
|
|
||||||
async updateAllSetting() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/xui/setting/update", this.allSetting);
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
await this.getAllSetting();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async updateUser() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/xui/setting/updateUser", this.user);
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.user = {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async restartPanel() {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
this.$confirm({
|
|
||||||
title: '{{ i18n "pages.setting.restartPanel" }}',
|
|
||||||
content: '{{ i18n "pages.setting.restartPanelDesc" }}',
|
|
||||||
okText: '{{ i18n "sure" }}',
|
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
|
||||||
onOk: () => resolve(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/xui/setting/restartPanel");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.loading(true);
|
|
||||||
await PromiseUtil.sleep(5000);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getUserSecret(){
|
|
||||||
const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
|
|
||||||
if (user_msg.success){
|
|
||||||
this.user = user_msg.obj;
|
|
||||||
}
|
|
||||||
this.loading(false);
|
|
||||||
},
|
|
||||||
async updateSecret(){
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
|
|
||||||
if (msg.success){
|
|
||||||
this.user = msg.obj;
|
|
||||||
}
|
|
||||||
this.loading(false);
|
|
||||||
await this.updateAllSetting();
|
|
||||||
},
|
|
||||||
async getNewSecret(){
|
|
||||||
this.loading(true);
|
|
||||||
await PromiseUtil.sleep(1000);
|
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
|
||||||
var string = '';
|
|
||||||
var len = 64;
|
|
||||||
for(var ii=0; ii<len; ii++){
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
this.user.loginSecret = string;
|
|
||||||
document.getElementById('token').value =this.user.loginSecret;
|
|
||||||
this.loading(false);
|
|
||||||
},
|
|
||||||
async toggleToken(value){
|
|
||||||
if(value)
|
|
||||||
this.getNewSecret();
|
|
||||||
else
|
|
||||||
this.user.loginSecret = "";
|
|
||||||
},
|
|
||||||
async resetXrayConfigToDefault() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkRequiredOutbounds() {
|
|
||||||
const newTemplateSettings = this.templateSettings;
|
|
||||||
const haveIPv4Outbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "IPv4");
|
|
||||||
const haveIPv4Rules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "IPv4");
|
|
||||||
const haveWARPOutbounds = newTemplateSettings.outbounds.some((o) => o?.tag === "WARP");
|
|
||||||
const haveWARPRules = newTemplateSettings.routing.rules.some((r) => r?.outboundTag === "WARP");
|
|
||||||
if (haveWARPRules && !haveWARPOutbounds) {
|
|
||||||
newTemplateSettings.outbounds.push(this.warpSettings);
|
|
||||||
}
|
|
||||||
if (haveIPv4Rules && !haveIPv4Outbounds) {
|
|
||||||
newTemplateSettings.outbounds.push(this.ipv4Settings);
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
},
|
|
||||||
templateRuleGetter(routeSettings) {
|
|
||||||
const { data, property, outboundTag } = routeSettings;
|
|
||||||
let result = false;
|
|
||||||
if (this.templateSettings != null) {
|
|
||||||
this.templateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
if (data.includes(routingRule[property][0])) {
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
templateRuleSetter(routeSettings) {
|
|
||||||
const { newValue, data, property, outboundTag } = routeSettings;
|
|
||||||
const oldTemplateSettings = this.templateSettings;
|
|
||||||
const newTemplateSettings = oldTemplateSettings;
|
|
||||||
if (newValue) {
|
|
||||||
const propertyRule = {
|
|
||||||
type: "field",
|
|
||||||
outboundTag,
|
|
||||||
[property]: data
|
|
||||||
};
|
|
||||||
newTemplateSettings.routing.rules.push(propertyRule);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const newRules = [];
|
|
||||||
newTemplateSettings.routing.rules.forEach(
|
|
||||||
(routingRule) => {
|
|
||||||
if (
|
|
||||||
routingRule.hasOwnProperty(property) &&
|
|
||||||
routingRule.hasOwnProperty("outboundTag") &&
|
|
||||||
routingRule.outboundTag === outboundTag
|
|
||||||
) {
|
|
||||||
if (data.includes(routingRule[property][0])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newRules.push(routingRule);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
newTemplateSettings.routing.rules = newRules;
|
|
||||||
}
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
this.checkRequiredOutbounds();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
await this.getAllSetting();
|
|
||||||
while (true) {
|
|
||||||
await PromiseUtil.sleep(1000);
|
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
templateSettings: {
|
|
||||||
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null; },
|
|
||||||
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
|
|
||||||
},
|
|
||||||
inboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.inbounds = JSON.parse(newValue)
|
|
||||||
this.templateSettings = newTemplateSettings
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outboundSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.outbounds = JSON.parse(newValue)
|
|
||||||
this.templateSettings = newTemplateSettings
|
|
||||||
},
|
|
||||||
},
|
|
||||||
routingRuleSettings: {
|
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
|
||||||
set: function (newValue) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.routing.rules = JSON.parse(newValue)
|
|
||||||
this.templateSettings = newTemplateSettings
|
|
||||||
},
|
|
||||||
},
|
|
||||||
torrentSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "protocol",
|
|
||||||
data: this.settingsData.protocols.bittorrent
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "protocol",
|
|
||||||
data: this.settingsData.protocols.bittorrent
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
privateIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.local
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.local
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AdsSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ads
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ads
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PornSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.porn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.porn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GoogleIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "IPv4",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.google
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "IPv4",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.google
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixIPv4Settings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "IPv4",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.netflix
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "IPv4",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.netflix
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IRIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
IRDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ChinaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.cn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.cn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ChinaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.cn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.cn
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RussiaIpSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.ru
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "ip",
|
|
||||||
data: this.settingsData.ips.ru
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RussiaDomainSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ru
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "blocked",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ru
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
GoogleWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.google
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.google
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
OpenAIWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.openai
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.openai
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
NetflixWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.netflix
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.netflix
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SpotifyWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.spotify
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.spotify
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
1086
web/html/xui/settings.html
Normal file
@@ -36,15 +36,21 @@ func (j *CheckClientIpJob) Run() {
|
|||||||
|
|
||||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
||||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
||||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
|
// check if file exists, if not create one
|
||||||
|
_, err := os.Stat(xray.GetBlockedIPsPath())
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
_, err = os.OpenFile(xray.GetBlockedIPsPath(), os.O_RDWR|os.O_CREATE, 0755)
|
||||||
|
checkError(err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||||
|
checkError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLogFile() {
|
func processLogFile() {
|
||||||
accessLogPath := GetAccessLogPath()
|
accessLogPath := GetAccessLogPath()
|
||||||
if accessLogPath == "" {
|
if accessLogPath == "" {
|
||||||
logger.Warning("xray log not init in config.json")
|
logger.Warning("access.log doesn't exist in your config.json")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +79,7 @@ func processLogFile() {
|
|||||||
if matchesEmail == "" {
|
if matchesEmail == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matchesEmail = strings.Split(matchesEmail, "email: ")[1]
|
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
|
||||||
|
|
||||||
if InboundClientIps[matchesEmail] != nil {
|
if InboundClientIps[matchesEmail] != nil {
|
||||||
if contains(InboundClientIps[matchesEmail], ip) {
|
if contains(InboundClientIps[matchesEmail], ip) {
|
||||||
@@ -94,9 +100,11 @@ func processLogFile() {
|
|||||||
sort.Strings(ips)
|
sort.Strings(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addInboundClientIps(clientEmail, ips)
|
addInboundClientIps(clientEmail, ips)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
// check if inbound connection is more than limited ip and drop connection
|
||||||
@@ -155,9 +163,6 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// Trim any leading/trailing whitespace from clientEmail
|
|
||||||
clientEmail = strings.TrimSpace(clientEmail)
|
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
|
|
||||||
@@ -199,8 +204,6 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
|||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
|
|
||||||
var disAllowedIps []string // initialize the slice
|
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email == clientEmail {
|
if client.Email == clientEmail {
|
||||||
|
|
||||||
@@ -222,7 +225,6 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableInbound(id int) error {
|
func DisableInbound(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
result := db.Model(model.Inbound{}).
|
result := db.Model(model.Inbound{}).
|
||||||
@@ -249,7 +251,6 @@ func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice() {
|
func LimitDevice() {
|
||||||
var destIp, destPort, srcIp, srcPort string
|
|
||||||
|
|
||||||
localIp, err := LocalIP()
|
localIp, err := LocalIP()
|
||||||
checkError(err)
|
checkError(err)
|
||||||
@@ -265,15 +266,15 @@ func LimitDevice() {
|
|||||||
|
|
||||||
data := strings.Split(row, " ")
|
data := strings.Split(row, " ")
|
||||||
|
|
||||||
if len(data) < 2 {
|
destIp, destPort, srcIp, srcPort := "", "", "", ""
|
||||||
continue // Skip this row if it doesn't have at least two elements
|
|
||||||
}
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
destIp = string(ipRegx.FindString(data[0]))
|
||||||
|
|
||||||
destPort = portRegx.FindString(data[0])
|
destPort = portRegx.FindString(data[0])
|
||||||
destPort = strings.Replace(destPort, ":", "", -1)
|
destPort = strings.Replace(destPort, ":", "", -1)
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
srcIp = string(ipRegx.FindString(data[1]))
|
||||||
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
srcPort = portRegx.FindString(data[1])
|
||||||
srcPort = strings.Replace(srcPort, ":", "", -1)
|
srcPort = strings.Replace(srcPort, ":", "", -1)
|
||||||
|
|
||||||
@@ -285,6 +286,7 @@ func LimitDevice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
func LocalIP() ([]string, error) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
@@ -24,7 +24,10 @@ func (j *CheckCpuJob) Run() {
|
|||||||
// get latest status of server
|
// get latest status of server
|
||||||
percent, err := cpu.Percent(1*time.Second, false)
|
percent, err := cpu.Percent(1*time.Second, false)
|
||||||
if err == nil && percent[0] > float64(threshold) {
|
if err == nil && percent[0] > float64(threshold) {
|
||||||
msg := fmt.Sprintf("🔴 CPU usage %.2f%% is more than threshold %d%%", percent[0], threshold)
|
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
|
||||||
|
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),
|
||||||
|
"Threshold=="+strconv.Itoa(threshold))
|
||||||
|
|
||||||
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
web/job/check_hash_storage.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckHashStorageJob struct {
|
||||||
|
tgbotService service.Tgbot
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckHashStorageJob() *CheckHashStorageJob {
|
||||||
|
return new(CheckHashStorageJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here Run is an interface method of the Job interface
|
||||||
|
func (j *CheckHashStorageJob) Run() {
|
||||||
|
// Remove expired hashes from storage
|
||||||
|
j.tgbotService.GetHashStorage().RemoveExpiredHashes()
|
||||||
|
}
|
||||||
144
web/locale/locale.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package locale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
"x-ui/logger"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
var i18nBundle *i18n.Bundle
|
||||||
|
var LocalizerWeb *i18n.Localizer
|
||||||
|
var LocalizerBot *i18n.Localizer
|
||||||
|
|
||||||
|
type I18nType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Bot I18nType = "bot"
|
||||||
|
Web I18nType = "web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingService interface {
|
||||||
|
GetTgLang() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||||
|
// set default bundle to english
|
||||||
|
i18nBundle = i18n.NewBundle(language.English)
|
||||||
|
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||||
|
|
||||||
|
// parse files
|
||||||
|
if err := parseTranslationFiles(i18nFS, i18nBundle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup bot locale
|
||||||
|
if err := initTGBotLocalizer(settingService); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
||||||
|
var sep string = "=="
|
||||||
|
if len(seperator) > 0 {
|
||||||
|
sep = seperator[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := make(map[string]interface{})
|
||||||
|
for _, param := range params {
|
||||||
|
parts := strings.SplitN(param, sep, 2)
|
||||||
|
templateData[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateData
|
||||||
|
}
|
||||||
|
|
||||||
|
func I18n(i18nType I18nType, key string, params ...string) string {
|
||||||
|
var localizer *i18n.Localizer
|
||||||
|
|
||||||
|
switch i18nType {
|
||||||
|
case "bot":
|
||||||
|
localizer = LocalizerBot
|
||||||
|
case "web":
|
||||||
|
localizer = LocalizerWeb
|
||||||
|
default:
|
||||||
|
logger.Errorf("Invalid type for I18n: %s", i18nType)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := createTemplateData(params)
|
||||||
|
|
||||||
|
msg, err := localizer.Localize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: key,
|
||||||
|
TemplateData: templateData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to localize message: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTGBotLocalizer(settingService SettingService) error {
|
||||||
|
botLang, err := settingService.GetTgLang()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LocalizerMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var lang string
|
||||||
|
|
||||||
|
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||||
|
lang = cookie.Value
|
||||||
|
} else {
|
||||||
|
lang = c.GetHeader("Accept-Language")
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizerWeb = i18n.NewLocalizer(i18nBundle, lang)
|
||||||
|
|
||||||
|
c.Set("localizer", LocalizerWeb)
|
||||||
|
c.Set("I18n", I18n)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||||
|
err := fs.WalkDir(i18nFS, "translation",
|
||||||
|
func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := i18nFS.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,20 +51,26 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, error) {
|
func (s *InboundService) GetClients(inbound *model.Inbound) ([]model.Client, error) {
|
||||||
settings := map[string][]model.Client{}
|
settings := map[string][]model.Client{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
if settings == nil {
|
if settings == nil {
|
||||||
@@ -110,7 +110,7 @@ func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) {
|
func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) {
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, err
|
|||||||
return inbound, common.NewError("Duplicate email:", existEmail)
|
return inbound, common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ func (s *InboundService) DelInbound(id int) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||||
clients, err := s.getClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -332,6 +332,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
client_key = "password"
|
client_key = "password"
|
||||||
}
|
}
|
||||||
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
client_key = "email"
|
||||||
|
}
|
||||||
|
|
||||||
inerfaceClients := settings["clients"].([]interface{})
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
@@ -369,7 +372,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
||||||
clients, err := s.getClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -387,7 +390,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.getClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -398,6 +401,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
oldClientId = oldClient.Email
|
||||||
} else {
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
}
|
}
|
||||||
@@ -595,6 +600,7 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
@@ -605,7 +611,8 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
|
||||||
|
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
DELETE FROM client_traffics
|
DELETE FROM client_traffics
|
||||||
@@ -616,6 +623,7 @@ func (s *InboundService) RemoveOrphanedTraffics() {
|
|||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -634,6 +642,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -664,6 +673,294 @@ func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
|
|||||||
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
|
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(traffics) > 0 {
|
||||||
|
inbound, err = s.GetInbound(traffics[0].InboundId)
|
||||||
|
return traffics[0], inbound, err
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(traffics) > 0 {
|
||||||
|
inbound, err = s.GetInbound(traffics[0].InboundId)
|
||||||
|
return traffics[0], inbound, err
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientByEmail(clientEmail string) (*xray.ClientTraffic, *model.Client, error) {
|
||||||
|
traffic, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return nil, nil, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.Email == clientEmail {
|
||||||
|
return traffic, &client, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) error {
|
||||||
|
traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return common.NewError("Inbound Not Found For Traffic ID:", trafficId)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientEmail := traffic.Email
|
||||||
|
|
||||||
|
oldClients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["tgId"] = tgId
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
clientOldEnabled := false
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
clientOldEnabled = oldClient.Enable
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["enable"] = !clientOldEnabled
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return !clientOldEnabled, s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) error {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["limitIp"] = count
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["expiryTime"] = expiry_time
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
|
Where("email = ?", clientEmail).
|
||||||
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
|
err := result.Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -803,7 +1100,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
|
|||||||
}
|
}
|
||||||
var emails []string
|
var emails []string
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.getClients(inbound)
|
clients, err := s.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Unable to get clients from inbound")
|
logger.Error("Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
@@ -953,7 +1250,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
}
|
}
|
||||||
// Add client traffic row for all clients which has email
|
// Add client traffic row for all clients which has email
|
||||||
modelClients, err := s.getClients(inbounds[inbound_index])
|
modelClients, err := s.GetClients(inbounds[inbound_index])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -972,3 +1269,8 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
// Remove orphaned traffics
|
// Remove orphaned traffics
|
||||||
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) MigrateDB() {
|
||||||
|
s.MigrationRequirements()
|
||||||
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -14,7 +15,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
"x-ui/util/sys"
|
"x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
@@ -73,7 +76,8 @@ type Release struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
xrayService XrayService
|
xrayService XrayService
|
||||||
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
@@ -175,7 +179,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
url := "https://api.github.com/repos/mhsanaei/Xray-core/releases"
|
url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -242,7 +246,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||||
url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -395,6 +399,106 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
|||||||
return fileContents, nil
|
return fileContents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) ImportDB(file multipart.File) error {
|
||||||
|
// Check if the file is a SQLite database
|
||||||
|
isValidDb, err := database.IsSQLiteDB(file)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error checking db file format: %v", err)
|
||||||
|
}
|
||||||
|
if !isValidDb {
|
||||||
|
return common.NewError("Invalid db file format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the file reader to the beginning
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error resetting file reader: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the file as temporary file
|
||||||
|
tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
|
||||||
|
// Remove the existing fallback file (if any) before creating one
|
||||||
|
_, err = os.Stat(tempPath)
|
||||||
|
if err == nil {
|
||||||
|
errRemove := os.Remove(tempPath)
|
||||||
|
if errRemove != nil {
|
||||||
|
return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the temporary file
|
||||||
|
tempFile, err := os.Create(tempPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error creating temporary db file: %v", err)
|
||||||
|
}
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
// Remove temp file before returning
|
||||||
|
defer os.Remove(tempPath)
|
||||||
|
|
||||||
|
// Save uploaded file to temporary file
|
||||||
|
_, err = io.Copy(tempFile, file)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error saving db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can init db or not
|
||||||
|
err = database.InitDB(tempPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error checking db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop Xray
|
||||||
|
s.StopXrayService()
|
||||||
|
|
||||||
|
// Backup the current database for fallback
|
||||||
|
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
||||||
|
// Remove the existing fallback file (if any)
|
||||||
|
_, err = os.Stat(fallbackPath)
|
||||||
|
if err == nil {
|
||||||
|
errRemove := os.Remove(fallbackPath)
|
||||||
|
if errRemove != nil {
|
||||||
|
return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Move the current database to the fallback location
|
||||||
|
err = os.Rename(config.GetDBPath(), fallbackPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error backing up temporary db file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the temporary file before returning
|
||||||
|
defer os.Remove(fallbackPath)
|
||||||
|
|
||||||
|
// Move temp to DB path
|
||||||
|
err = os.Rename(tempPath, config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
errRename := os.Rename(fallbackPath, config.GetDBPath())
|
||||||
|
if errRename != nil {
|
||||||
|
return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
|
||||||
|
}
|
||||||
|
return common.NewErrorf("Error moving db file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate DB
|
||||||
|
err = database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
errRename := os.Rename(fallbackPath, config.GetDBPath())
|
||||||
|
if errRename != nil {
|
||||||
|
return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
|
||||||
|
}
|
||||||
|
return common.NewErrorf("Error migrating db: %v", err)
|
||||||
|
}
|
||||||
|
s.inboundService.MigrateDB()
|
||||||
|
|
||||||
|
// Start Xray
|
||||||
|
err = s.RestartXrayService()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Imported DB but Failed to start Xray: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||||
// Run the command
|
// Run the command
|
||||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
|
|||||||
@@ -39,7 +39,16 @@ var defaultValueMap = map[string]string{
|
|||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
|
"tgLang": "en-US",
|
||||||
"secretEnable": "false",
|
"secretEnable": "false",
|
||||||
|
"subEnable": "false",
|
||||||
|
"subListen": "",
|
||||||
|
"subPort": "2096",
|
||||||
|
"subPath": "sub/",
|
||||||
|
"subDomain": "",
|
||||||
|
"subCertFile": "",
|
||||||
|
"subKeyFile": "",
|
||||||
|
"subUpdates": "12",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -256,6 +265,10 @@ func (s *SettingService) GetTgCpu() (int, error) {
|
|||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgLang() (string, error) {
|
||||||
|
return s.getString("tgLang")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
@@ -331,6 +344,48 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
|||||||
return location, nil
|
return location, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubEnable() (bool, error) {
|
||||||
|
return s.getBool("subEnable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubListen() (string, error) {
|
||||||
|
return s.getString("subListen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPort() (int, error) {
|
||||||
|
return s.getInt("subPort")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
|
subPath, err := s.getString("subPath")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return subPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
|
return s.getString("subDomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubCertFile() (string, error) {
|
||||||
|
return s.getString("subCertFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||||
|
return s.getString("subKeyFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||||
|
return s.getInt("subUpdates")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
if err := allSetting.CheckValid(); err != nil {
|
if err := allSetting.CheckValid(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"search" = "Search"
|
"search" = "Search"
|
||||||
|
"filter" = "Filter"
|
||||||
"loading" = "Loading"
|
"loading" = "Loading"
|
||||||
"second" = "Second"
|
"second" = "Second"
|
||||||
"minute" = "Minute"
|
"minute" = "Minute"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"unlimited" = "Unlimited"
|
"unlimited" = "Unlimited"
|
||||||
"none" = "None"
|
"none" = "None"
|
||||||
"qrCode" = "QR Code"
|
"qrCode" = "QR Code"
|
||||||
"info" = "More information"
|
"info" = "More Information"
|
||||||
"edit" = "Edit"
|
"edit" = "Edit"
|
||||||
"delete" = "Delete"
|
"delete" = "Delete"
|
||||||
"reset" = "Reset"
|
"reset" = "Reset"
|
||||||
@@ -43,29 +43,29 @@
|
|||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
"success" = " Success"
|
"success" = "Success"
|
||||||
"getVersion" = "Get version"
|
"getVersion" = "Get version"
|
||||||
"install" = "Install"
|
"install" = "Install"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
"usage" = "Usage"
|
"usage" = "Usage"
|
||||||
"secretToken" = "Secret token"
|
"secretToken" = "Secret Token"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"setting" = "Panel Setting"
|
"settings" = "Panel Settings"
|
||||||
"logout" = "Logout"
|
"logout" = "Logout"
|
||||||
"link" = "Other"
|
"link" = "Other"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"title" = "Login"
|
"title" = "Login"
|
||||||
"loginAgain" = "The login time limit has expired, please log in again"
|
"loginAgain" = "The login time limit has expired. Please log in again."
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
"invalidFormData" = "Input Data Format is Invalid"
|
"invalidFormData" = "Input data format is invalid."
|
||||||
"emptyUsername" = "Please Enter Username"
|
"emptyUsername" = "Please enter username."
|
||||||
"emptyPassword" = "Please Enter Password"
|
"emptyPassword" = "Please enter password."
|
||||||
"wrongUsernameOrPassword" = "Invalid username or password"
|
"wrongUsernameOrPassword" = "Invalid username or password."
|
||||||
"successLogin" = "Login"
|
"successLogin" = "Login"
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
@@ -81,21 +81,28 @@
|
|||||||
"operationHours" = "Operation Hours"
|
"operationHours" = "Operation Hours"
|
||||||
"operationHoursDesc" = "System uptime: time since startup."
|
"operationHoursDesc" = "System uptime: time since startup."
|
||||||
"systemLoad" = "System Load"
|
"systemLoad" = "System Load"
|
||||||
"connectionCount" = "Number of connections"
|
"connectionCount" = "Number of Connections"
|
||||||
"connectionCountDesc" = "Total connections across all network cards"
|
"connectionCountDesc" = "Total connections across all network cards."
|
||||||
"upSpeed" = "Total upload speed for all network cards"
|
"upSpeed" = "Total upload speed for all network cards."
|
||||||
"downSpeed" = "Total download speed for all network cards"
|
"downSpeed" = "Total download speed for all network cards."
|
||||||
"totalSent" = "Total upload traffic of all network cards since system startup"
|
"totalSent" = "Total upload traffic of all network cards since system startup."
|
||||||
"totalReceive" = "Total download data across all network cards since system startup"
|
"totalReceive" = "Total download data across all network cards since system startup."
|
||||||
"xraySwitchVersionDialog" = "Switch xray version"
|
"xraySwitchVersionDialog" = "Switch Xray Version"
|
||||||
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
"xraySwitchVersionDialogDesc" = "Are you sure you want to switch the Xray version to"
|
||||||
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
"dontRefresh" = "Installation is in progress, please do not refresh this page."
|
||||||
|
"logs" = "Logs"
|
||||||
|
"config" = "Config"
|
||||||
|
"backup" = "Backup & Restore"
|
||||||
|
"backupTitle" = "Backup & Restore Database"
|
||||||
|
"backupDescription" = "Remember to backup before importing a new database."
|
||||||
|
"exportDatabase" = "Download Database"
|
||||||
|
"importDatabase" = "Upload Database"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "Inbounds"
|
"title" = "Inbounds"
|
||||||
"totalDownUp" = "Total uploads/downloads"
|
"totalDownUp" = "Total Uploads/Downloads"
|
||||||
"totalUsage" = "Total usage"
|
"totalUsage" = "Total Usage"
|
||||||
"inboundCount" = "Number of inbound"
|
"inboundCount" = "Number of Inbounds"
|
||||||
"operate" = "Menu"
|
"operate" = "Menu"
|
||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
@@ -104,77 +111,77 @@
|
|||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Details"
|
"details" = "Details"
|
||||||
"transportConfig" = "Transport"
|
"transportConfig" = "Transport"
|
||||||
"expireDate" = "Expire date"
|
"expireDate" = "Expire Date"
|
||||||
"resetTraffic" = "Reset traffic"
|
"resetTraffic" = "Reset Traffic"
|
||||||
"addInbound" = "Add Inbound"
|
"addInbound" = "Add Inbound"
|
||||||
"generalActions" = "General Actions"
|
"generalActions" = "General Actions"
|
||||||
"addTo" = "Create"
|
"create" = "Create"
|
||||||
"revise" = "Update"
|
"update" = "Update"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify Inbound"
|
||||||
"deleteInbound" = "Delete Inbound"
|
"deleteInbound" = "Delete Inbound"
|
||||||
"deleteInboundContent" = "Confirm deletion of inbound?"
|
"deleteInboundContent" = "Confirm deletion of inbound?"
|
||||||
"resetTrafficContent" = "Confirm traffic reset?"
|
"resetTrafficContent" = "Confirm traffic reset?"
|
||||||
"copyLink" = "Copy Link"
|
"copyLink" = "Copy Link"
|
||||||
"address" = "Address"
|
"address" = "Address"
|
||||||
"network" = "Network"
|
"network" = "Network"
|
||||||
"destinationPort" = "Destination port"
|
"destinationPort" = "Destination Port"
|
||||||
"targetAddress" = "Target address"
|
"targetAddress" = "Target Address"
|
||||||
"disableInsecureEncryption" = "Disable insecure encryption"
|
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "Means no limit"
|
"meansNoLimit" = "Means No Limit"
|
||||||
"totalFlow" = "Total flow"
|
"totalFlow" = "Total Flow"
|
||||||
"leaveBlankToNeverExpire" = "Leave blank to set no expiration"
|
"leaveBlankToNeverExpire" = "Leave Blank to Never Expire"
|
||||||
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
|
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
|
||||||
"certificatePath" = "Certificate file path"
|
"certificatePath" = "Certificate File Path"
|
||||||
"certificateContent" = "Certificate file content"
|
"certificateContent" = "Certificate File Content"
|
||||||
"publicKeyPath" = "Public key path"
|
"publicKeyPath" = "Public Key Path"
|
||||||
"publicKeyContent" = "Public key content"
|
"publicKeyContent" = "Public Key Content"
|
||||||
"keyPath" = "Private Key path"
|
"keyPath" = "Private Key Path"
|
||||||
"keyContent" = "Private Key content"
|
"keyContent" = "Private Key Content"
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export links"
|
"export" = "Export Links"
|
||||||
"Clone" = "Clone"
|
"clone" = "Clone"
|
||||||
"cloneInbound" = "Create"
|
"cloneInbound" = "Clone"
|
||||||
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone"
|
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone."
|
||||||
"cloneInboundOk" = "Creating a clone from"
|
"cloneInboundOk" = "Clone"
|
||||||
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
||||||
"resetAllTrafficOkText" = "Confirm"
|
|
||||||
"resetAllTrafficCancelText" = "Cancel"
|
|
||||||
"IPLimit" = "IP Limit"
|
|
||||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (Enter 0 to disable IP limit)"
|
|
||||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
"resetInboundClientTrafficTitle" = "Reset all clients traffic"
|
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
||||||
"resetInboundClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
||||||
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
||||||
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of all clients ?"
|
"resetAllClientTrafficContent" = "Are you sure you want to reset all traffics for all clients?"
|
||||||
"delDepletedClients" = "Delete depleted clients"
|
"delDepletedClients" = "Delete Depleted Clients"
|
||||||
"delDepletedClientsTitle" = "Delete depleted clients"
|
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||||
"delDepletedClientsContent" = "Are you sure to delete all depleted clients ?"
|
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
|
||||||
"Email" = "Email"
|
"email" = "Email"
|
||||||
"EmailDesc" = "Please provide a unique email address"
|
"emailDesc" = "Please provide a unique email address."
|
||||||
|
"IPLimit" = "IP Limit"
|
||||||
|
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
||||||
"IPLimitlog" = "IP Log"
|
"IPLimitlog" = "IP Log"
|
||||||
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
"IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)."
|
||||||
"IPLimitlogclear" = "Clear The Log"
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"XTLSdec" = "Xray core needs to be 1.7.5"
|
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
||||||
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
||||||
|
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )"
|
||||||
|
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add client"
|
"add" = "Add Client"
|
||||||
"edit" = "Edit client"
|
"edit" = "Edit Client"
|
||||||
"submitAdd" = "Add client"
|
"submitAdd" = "Add Client"
|
||||||
"submitEdit" = "Save changes"
|
"submitEdit" = "Save changes"
|
||||||
"clientCount" = "Number of clients"
|
"clientCount" = "Number of Clients"
|
||||||
"bulk" = "Add bulk"
|
"bulk" = "Add Bulk"
|
||||||
"method" = "Method"
|
"method" = "Method"
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
"last" = "Last"
|
"last" = "Last"
|
||||||
"prefix" = "Prefix"
|
"prefix" = "Prefix"
|
||||||
"postfix" = "postfix"
|
"postfix" = "Postfix"
|
||||||
"delayedStart" = "Start after first use"
|
"delayedStart" = "Start after first use"
|
||||||
"expireDays" = "Expire days"
|
"expireDays" = "Expire days"
|
||||||
"days" = "day(s)"
|
"days" = "day(s)"
|
||||||
@@ -199,113 +206,279 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "Encryption"
|
"encryption" = "Encryption"
|
||||||
|
|
||||||
[pages.setting]
|
[pages.settings]
|
||||||
"title" = "Setting"
|
"title" = "Settings"
|
||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
"restartPanel" = "Restart Panel"
|
"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes."
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
"restartPanel" = "Restart Panel "
|
||||||
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
||||||
"actions" = "Actions"
|
"actions" = "Actions"
|
||||||
"resetDefaultConfig" = "Reset to default config"
|
"resetDefaultConfig" = "Reset to Default Configuration"
|
||||||
"panelConfig" = "Panel Configuration"
|
"panelSettings" = "Panel Settings"
|
||||||
"userSetting" = "User Setting"
|
"securitySettings" = "Security Settings"
|
||||||
"xrayConfiguration" = "Xray Configuration"
|
"xrayConfiguration" = "Xray Configuration"
|
||||||
"TGReminder" = "TG Reminder Related Settings"
|
"TGBotSettings" = "Telegram Bot Settings"
|
||||||
"otherSetting" = "Other Setting"
|
"panelListeningIP" = "Panel Listening IP"
|
||||||
"panelListeningIP" = "Panel listening IP"
|
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
||||||
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs, restart the panel to take effect"
|
|
||||||
"panelPort" = "Panel Port"
|
"panelPort" = "Panel Port"
|
||||||
"panelPortDesc" = "Restart the panel to take effect"
|
"panelPortDesc" = "The port used to display this panel"
|
||||||
"publicKeyPath" = "Panel certificate public key file path"
|
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"publicKeyPathDesc" = "Fill in an absolute path starting with."
|
||||||
"privateKeyPath" = "Panel certificate private key file path"
|
"privateKeyPath" = "Panel Certificate Private Key File Path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"privateKeyPathDesc" = "Fill in an absolute path starting with."
|
||||||
"panelUrlPath" = "panel url root path"
|
"panelUrlPath" = "Panel URL Root Path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
"panelUrlPathDesc" = "Must start with '/' and end with."
|
||||||
"oldUsername" = "Current Username"
|
"oldUsername" = "Current Username"
|
||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
"newUsername" = "New Username"
|
"newUsername" = "New Username"
|
||||||
"newPassword" = "New Password"
|
"newPassword" = "New Password"
|
||||||
"basicTemplate" = "Basic Template"
|
"telegramBotEnable" = "Enable Telegram bot"
|
||||||
"advancedTemplate" = "Advanced Template parts"
|
"telegramBotEnableDesc" = "Connect to the features of this panel through the Telegram bot"
|
||||||
"completeTemplate" = "Complete Template of Xray configuration"
|
|
||||||
"generalConfigs" = "General Configs"
|
|
||||||
"generalConfigsDesc" = "This options will prevent users from connecting to specific protocols and websites."
|
|
||||||
"countryConfigs" = "Country Configs"
|
|
||||||
"countryConfigsDesc" = "This options will prevent users from connecting to specific country domains."
|
|
||||||
"ipv4Configs" = "IPv4 Configs"
|
|
||||||
"ipv4ConfigsDesc" = "This options will be route to target domains only via IPv4."
|
|
||||||
"warpConfigs" = "WARP Configs"
|
|
||||||
"warpConfigsDesc" = "Caution: Before using this options, Install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
|
||||||
"xrayConfigTemplate" = "Xray Configuration Template"
|
|
||||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
|
||||||
"xrayConfigTorrent" = "Ban bittorrent usage"
|
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
|
|
||||||
"xrayConfigPrivateIp" = "Ban private IP ranges to connect"
|
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigAds" = "Block Ads"
|
|
||||||
"xrayConfigAdsDesc" = "Change the configuration template to block Ads, restart the panel to take effect"
|
|
||||||
"xrayConfigPorn" = "Block Porn Websites"
|
|
||||||
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to Porn websites, restart the panel to take effect"
|
|
||||||
"xrayConfigIRIp" = "Ban Iran IP ranges to connect"
|
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigIRDomain" = "Ban Iran Domains to connect"
|
|
||||||
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains, restart the panel to take effect"
|
|
||||||
"xrayConfigChinaIp" = "Ban China IP ranges to connect"
|
|
||||||
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigChinaDomain" = "Ban China Domains to connect"
|
|
||||||
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains, restart the panel to take effect"
|
|
||||||
"xrayConfigRussiaIp" = "Ban Russia IP ranges to connect"
|
|
||||||
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigRussiaDomain" = "Ban Russia Domains to connect"
|
|
||||||
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains, restart the panel to take effect"
|
|
||||||
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
|
||||||
"xrayConfigGoogleIPv4Desc" = "Add routing for google to connect with IPv4, restart the panel to take effect"
|
|
||||||
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
|
||||||
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4, restart the panel to take effect"
|
|
||||||
"xrayConfigGoogleWARP" = "Route Google to WARP"
|
|
||||||
"xrayConfigGoogleWARPDesc" = "Add routing for Google to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) to WARP"
|
|
||||||
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigNetflixWARP" = "Route Netflix to WARP"
|
|
||||||
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigSpotifyWARP" = "Route Spotify to WARP"
|
|
||||||
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigIRWARP" = "Route Iran Domains to WARP"
|
|
||||||
"xrayConfigIRWARPDesc" = "Add routing for Iran Domains to WARP. restart the panel to take effect"
|
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
|
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
|
||||||
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server, restart the panel to take effect"
|
|
||||||
"xrayConfigRoutings" = "Configuration of Routing rules"
|
|
||||||
"xrayConfigRoutingsDesc" = "Change the configuration template to define Routing rules for this server, restart the panel to take effect"
|
|
||||||
"telegramBotEnable" = "Enable telegram bot"
|
|
||||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
"telegramTokenDesc" = "Restart the panel to take effect"
|
"telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather"
|
||||||
"telegramChatId" = "Telegram Admin ChatIds"
|
"telegramChatId" = "Telegram Admin Chat IDs"
|
||||||
"telegramChatIdDesc" = "Multi chatIDs separated by comma. Restart the panel to take effect"
|
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot to get your Chat IDs."
|
||||||
"telegramNotifyTime" = "Telegram bot notification time"
|
"telegramNotifyTime" = "Telegram bot notification time"
|
||||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
"telegramNotifyTimeDesc" = "Use Crontab timing format."
|
||||||
"tgNotifyBackup" = "Database backup"
|
"tgNotifyBackup" = "Database Backup"
|
||||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
"tgNotifyBackupDesc" = "Include database backup file with report notification."
|
||||||
"sessionMaxAge" = "Session maximum age"
|
"sessionMaxAge" = "Session maximum age"
|
||||||
"sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
|
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
||||||
"expireTimeDiff" = "Exhaustion time threshold"
|
"expireTimeDiff" = "Expiration threshold for notification"
|
||||||
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
"expireTimeDiffDesc" = "Get notified about account expiration before the threshold (unit: day)"
|
||||||
"trafficDiff" = "Exhaustion traffic threshold"
|
"trafficDiff" = "Traffic threshold for notification"
|
||||||
"trafficDiffDesc" = "Detect exhaustion before finishing traffic (unit:GB)"
|
"trafficDiffDesc" = "Get notified about traffic exhaustion before reaching the threshold (unit: GB)"
|
||||||
"tgNotifyCpu" = "CPU percentage alert threshold"
|
"tgNotifyCpu" = "CPU percentage alert threshold"
|
||||||
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
||||||
"timeZonee" = "Time Zone"
|
"timeZone" = "Time zone"
|
||||||
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
|
||||||
"loginSecurity" = "Login security"
|
"subSettings" = "Subscription"
|
||||||
"loginSecurityDesc" = "Toggle additional step in user login page"
|
"subEnable" = "Enable service"
|
||||||
"secretToken" = "Secret Token"
|
"subEnableDesc" = "Subscription feature with separate configuration"
|
||||||
"secretTokenDesc" = "Copy this secret token and keep it in a safe place; without this you won't be able to login. This can not be recovered from x-ui command tool neither"
|
"subListen" = "Listening IP"
|
||||||
|
"subListenDesc" = "Leave blank by default to monitor all IPs"
|
||||||
|
"subPort" = "Subscription Port"
|
||||||
|
"subPortDesc" = "Port number for serving the subscription service must be unused in server"
|
||||||
|
"subCertPath" = "Subscription Certificate Public Key File Path"
|
||||||
|
"subCertPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subKeyPath" = "Subscription Certificate Private Key File Path"
|
||||||
|
"subKeyPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subPath" = "Subscription URL Root Path"
|
||||||
|
"subPathDesc" = "Must start with '/' and end with '/'"
|
||||||
|
"subDomain" = "Listening Domain"
|
||||||
|
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||||
|
"subUpdates" = "Subscription update intervals"
|
||||||
|
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.settings.templates]
|
||||||
"modifySetting" = "Modify setting"
|
"title" = "Templates"
|
||||||
"getSetting" = "Get setting"
|
"basicTemplate" = "Basic Template"
|
||||||
"modifyUser" = "Modify user"
|
"advancedTemplate" = "Advanced Template"
|
||||||
"originalUserPassIncorrect" = "The original user name or original password is incorrect"
|
"completeTemplate" = "Complete Template"
|
||||||
|
"generalConfigs" = "General Configs"
|
||||||
|
"generalConfigsDesc" = "These options will provide general adjustments."
|
||||||
|
"blockConfigs" = "Blocking Configs"
|
||||||
|
"blockConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
|
||||||
|
"blockCountryConfigs" = "Block Country Configs"
|
||||||
|
"blockCountryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
|
||||||
|
"directCountryConfigs" = "Direct Country Configs"
|
||||||
|
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
|
||||||
|
"ipv4Configs" = "IPv4 Configs"
|
||||||
|
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
||||||
|
"warpConfigs" = "WARP Configs"
|
||||||
|
"warpConfigsDesc" = "Caution: Before using these options, install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
||||||
|
"xrayConfigTemplate" = "Xray Configuration Template"
|
||||||
|
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template."
|
||||||
|
"xrayConfigFreedomStrategy" = "Configure Strategy for Freedom Protocol"
|
||||||
|
"xrayConfigFreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
|
||||||
|
"xrayConfigRoutingStrategy" = "Configure Domains Routing Strategy"
|
||||||
|
"xrayConfigRoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
|
||||||
|
"xrayConfigTorrent" = "Ban BitTorrent Usage"
|
||||||
|
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
|
||||||
|
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
|
||||||
|
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
|
||||||
|
"xrayConfigAds" = "Block Ads"
|
||||||
|
"xrayConfigAdsDesc" = "Change the configuration template to block ads."
|
||||||
|
"xrayConfigFamily" = "Block Malware and Adult Content"
|
||||||
|
"xrayConfigFamilyDesc" = "DNS resolvers to block malware and adult content for family protection."
|
||||||
|
"xrayConfigSpeedtest" = "Block Speedtest Websites"
|
||||||
|
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites."
|
||||||
|
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
||||||
|
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
|
||||||
|
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
||||||
|
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
|
||||||
|
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
|
||||||
|
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
|
||||||
|
"xrayConfigChinaDomain" = "Disable connection to China domains"
|
||||||
|
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
|
||||||
|
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
|
||||||
|
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
|
||||||
|
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
|
||||||
|
"xrayConfigDirectIRIp" = "Direct connection to Iran IP ranges"
|
||||||
|
"xrayConfigDirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
|
||||||
|
"xrayConfigDirectIRDomain" = "Direct connection to Iran domains"
|
||||||
|
"xrayConfigDirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
|
||||||
|
"xrayConfigDirectChinaIp" = "Direct connection to China IP ranges"
|
||||||
|
"xrayConfigDirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
|
||||||
|
"xrayConfigDirectChinaDomain" = "Direct connection to China domains"
|
||||||
|
"xrayConfigDirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
|
||||||
|
"xrayConfigDirectRussiaIp" = "Direct connection to Russia IP ranges"
|
||||||
|
"xrayConfigDirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
|
||||||
|
"xrayConfigDirectRussiaDomain" = "Direct connection to Russia domains"
|
||||||
|
"xrayConfigDirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
|
||||||
|
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
|
||||||
|
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
|
||||||
|
"xrayConfigGoogleWARP" = "Route Google through WARP."
|
||||||
|
"xrayConfigGoogleWARPDesc" = "Add routing for Google via WARP."
|
||||||
|
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) through WARP."
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP."
|
||||||
|
"xrayConfigNetflixWARP" = "Route Netflix through WARP."
|
||||||
|
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix via WARP."
|
||||||
|
"xrayConfigSpotifyWARP" = "Route Spotify through WARP."
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify via WARP."
|
||||||
|
"xrayConfigIRWARP" = "Route Iran domains through WARP."
|
||||||
|
"xrayConfigIRWARPDesc" = "Add routing for Iran domains via WARP."
|
||||||
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
|
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients."
|
||||||
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
|
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server."
|
||||||
|
"xrayConfigRoutings" = "Configuration of routing rules."
|
||||||
|
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server."
|
||||||
|
"manualLists" = "Manual Lists"
|
||||||
|
"manualListsDesc" = "Please use the JSON array format."
|
||||||
|
"manualBlockedIPs" = "List of Blocked IPs"
|
||||||
|
"manualBlockedDomains" = "List of Blocked Domains"
|
||||||
|
"manualDirectIPs" = "List of Direct IPs"
|
||||||
|
"manualDirectDomains" = "List of Direct Domains"
|
||||||
|
"manualIPv4Domains" = "List of IPv4 Domains"
|
||||||
|
"manualWARPDomains" = "List of WARP Domains"
|
||||||
|
|
||||||
|
[pages.settings.security]
|
||||||
|
"admin" = "Admin"
|
||||||
|
"secret" = "Secret Token"
|
||||||
|
"loginSecurity" = "Login security"
|
||||||
|
"loginSecurityDesc" = "Enable additional user login security step"
|
||||||
|
"secretToken" = "Secret Token"
|
||||||
|
"secretTokenDesc" = "Please copy and securely store this token in a safe place. This token is required for login and cannot be recovered from the x-ui command tool."
|
||||||
|
|
||||||
|
[pages.settings.toasts]
|
||||||
|
"modifySettings" = "Modify Settings "
|
||||||
|
"getSettings" = "Get Settings "
|
||||||
|
"modifyUser" = "Modify User "
|
||||||
|
"originalUserPassIncorrect" = "Incorrect original username or password"
|
||||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ Custom keyboard closed!"
|
||||||
|
"noResult" = "❗ No result!"
|
||||||
|
"noQuery" = "❌ Query not found! Please use the command again!"
|
||||||
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
|
"noInbounds" = "❗ No inbound found!"
|
||||||
|
"unlimited" = "♾ Unlimited"
|
||||||
|
"month" = "Month"
|
||||||
|
"months" = "Months"
|
||||||
|
"day" = "Day"
|
||||||
|
"days" = "Days"
|
||||||
|
"unknown" = "Unknown"
|
||||||
|
"inbounds" = "Inbounds"
|
||||||
|
"clients" = "Clients"
|
||||||
|
|
||||||
|
[tgbot.commands]
|
||||||
|
"unknown" = "❗ Unknown command"
|
||||||
|
"pleaseChoose" = "👇 Please choose:\r\n"
|
||||||
|
"help" = "🤖 Welcome to this bot! It's designed to offer you specific data from the server, and it allows you to make modifications as needed.\r\n\r\n"
|
||||||
|
"start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n"
|
||||||
|
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
|
||||||
|
"status" = "✅ Bot is ok!"
|
||||||
|
"usage" = "❗ Please provide a text to search!"
|
||||||
|
"helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>"
|
||||||
|
"helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
|
||||||
|
|
||||||
|
[tgbot.messages]
|
||||||
|
"cpuThreshold" = "🔴 The CPU usage {{ .Percent }}% is more than threshold {{ .Threshold }}%"
|
||||||
|
"selectUserFailed" = "❌ Error in user selection!"
|
||||||
|
"userSaved" = "✅ Telegram User saved."
|
||||||
|
"loginSuccess" = "✅ Successfully logged-in to the panel.\r\n"
|
||||||
|
"loginFailed" = "❗️ Login to the panel failed.\r\n"
|
||||||
|
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
|
||||||
|
"datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n"
|
||||||
|
"hostname" = "💻 Hostname: {{ .Hostname }}\r\n"
|
||||||
|
"version" = "🚀 X-UI Version: {{ .Version }}\r\n"
|
||||||
|
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||||
|
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||||
|
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||||
|
"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n"
|
||||||
|
"serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||||
|
"serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||||
|
"serverMemory" = "📋 Server Memory: {{ .Current }}/{{ .Total }}\r\n"
|
||||||
|
"tcpCount" = "🔹 TcpCount: {{ .Count }}\r\n"
|
||||||
|
"udpCount" = "🔸 UdpCount: {{ .Count }}\r\n"
|
||||||
|
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||||
|
"xrayStatus" = "ℹ️ Xray Status: {{ .State }}\r\n"
|
||||||
|
"username" = "👤 Username: {{ .Username }}\r\n"
|
||||||
|
"time" = "⏰ Time: {{ .Time }}\r\n"
|
||||||
|
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||||
|
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||||
|
"expire" = "📅 Expire Date: {{ .DateTime }}\r\n \r\n"
|
||||||
|
"expireIn" = "📅 Expire In: {{ .Time }}\r\n \r\n"
|
||||||
|
"active" = "💡 Active: {{ .Enable }}\r\n"
|
||||||
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
|
"upload" = "🔼 Upload↑: {{ .Upload }}\r\n"
|
||||||
|
"download" = "🔽 Download↓: {{ .Download }}\r\n"
|
||||||
|
"total" = "🔄 Total: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
|
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
||||||
|
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "🔄🕒 Refreshed On: {{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ Close Keyboard"
|
||||||
|
"cancel" = "❌ Cancel"
|
||||||
|
"cancelReset" = "❌ Cancel Reset"
|
||||||
|
"cancelIpLimit" = "❌ Cancel IP Limit"
|
||||||
|
"confirmResetTraffic" = "✅ Confirm Reset Traffic?"
|
||||||
|
"confirmClearIps" = "✅ Confirm Clear IPs?"
|
||||||
|
"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?"
|
||||||
|
"dbBackup" = "Get DB Backup"
|
||||||
|
"serverUsage" = "Server Usage"
|
||||||
|
"getInbounds" = "Get Inbounds"
|
||||||
|
"depleteSoon" = "Deplete soon"
|
||||||
|
"clientUsage" = "Get Usage"
|
||||||
|
"commands" = "Commands"
|
||||||
|
"refresh" = "🔄 Refresh"
|
||||||
|
"clearIPs" = "❌ Clear IPs"
|
||||||
|
"removeTGUser" = "❌ Remove Telegram User"
|
||||||
|
"selectTGUser" = "👤 Select Telegram User"
|
||||||
|
"selectOneTGUser" = "👤 Select a telegram user:"
|
||||||
|
"resetTraffic" = "📈 Reset Traffic"
|
||||||
|
"resetExpire" = "📅 Reset Expire Days"
|
||||||
|
"ipLog" = "🔢 IP Log"
|
||||||
|
"ipLimit" = "🔢 IP Limit"
|
||||||
|
"setTGUser" = "👤 Set Telegram User"
|
||||||
|
"toggle" = "🔘 Enable / Disable"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ Error in Operation."
|
||||||
|
"getInboundsFailed" = "❌ Failed to get inbounds"
|
||||||
|
"canceled" = "❌ {{ .Email }} : Operation canceled."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : Client refreshed successfully."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : IPs refreshed successfully."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Client's Telegram User refreshed successfully."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : Traffic reset successfully."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : Expire days reset successfully."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : IP limit {{ .Count }} saved successfully."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : IPs cleared successfully."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : Get IP Log."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : Get Telegram User Info."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : Enabled successfully."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : Disabled successfully."
|
||||||
|
"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"enable" = "فعال"
|
"enable" = "فعال"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"search" = "جستجو"
|
"search" = "جستجو"
|
||||||
|
"filter" = "فیلتر"
|
||||||
"loading" = "در حال بروزرسانی..."
|
"loading" = "در حال بروزرسانی..."
|
||||||
"second" = "ثانیه"
|
"second" = "ثانیه"
|
||||||
"minute" = "دقیقه"
|
"minute" = "دقیقه"
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
"inbounds" = "سرویس ها"
|
"inbounds" = "سرویس ها"
|
||||||
"setting" = "تنظیمات پنل"
|
"settings" = "تنظیمات پنل"
|
||||||
"logout" = "خروج"
|
"logout" = "خروج"
|
||||||
"link" = "دیگر"
|
"link" = "دیگر"
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"restartXray" = "شروع مجدد"
|
"restartXray" = "شروع مجدد"
|
||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد "
|
||||||
"operationHours" = "مدت فعالیت"
|
"operationHours" = "مدت فعالیت"
|
||||||
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
||||||
"systemLoad" = "بار روی سیستم"
|
"systemLoad" = "بار روی سیستم"
|
||||||
@@ -89,7 +89,14 @@
|
|||||||
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
||||||
"xraySwitchVersionDialog" = "تغییر ورژن"
|
"xraySwitchVersionDialog" = "تغییر ورژن"
|
||||||
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
||||||
"dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید "
|
"dontRefresh" = "در حال نصب ، لطفا رفرش نکنید "
|
||||||
|
"logs" = "گزارش ها"
|
||||||
|
"config" = "تنظیمات"
|
||||||
|
"backup" = "پشتیبان گیری و بازیابی"
|
||||||
|
"backupTitle" = "پشتیبان گیری و بازیابی دیتابیس"
|
||||||
|
"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید"
|
||||||
|
"exportDatabase" = "دانلود دیتابیس"
|
||||||
|
"importDatabase" = "آپلود دیتابیس"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "کاربران"
|
"title" = "کاربران"
|
||||||
@@ -108,8 +115,8 @@
|
|||||||
"resetTraffic" = "ریست ترافیک"
|
"resetTraffic" = "ریست ترافیک"
|
||||||
"addInbound" = "اضافه کردن سرویس"
|
"addInbound" = "اضافه کردن سرویس"
|
||||||
"generalActions" = "عملیات کلی"
|
"generalActions" = "عملیات کلی"
|
||||||
"addTo" = "اضافه کردن"
|
"create" = "اضافه کردن"
|
||||||
"revise" = "ویرایش"
|
"update" = "ویرایش"
|
||||||
"modifyInbound" = "ویرایش سرویس"
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
"deleteInbound" = "حذف سرویس"
|
"deleteInbound" = "حذف سرویس"
|
||||||
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
||||||
@@ -134,7 +141,7 @@
|
|||||||
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
"Clone" = "شبیه سازی"
|
"clone" = "شبیه سازی"
|
||||||
"cloneInbound" = "ایجاد"
|
"cloneInbound" = "ایجاد"
|
||||||
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
||||||
"cloneInboundOk" = "ساختن شبیه ساز"
|
"cloneInboundOk" = "ساختن شبیه ساز"
|
||||||
@@ -150,16 +157,18 @@
|
|||||||
"delDepletedClients" = "حذف کاربران منقضی"
|
"delDepletedClients" = "حذف کاربران منقضی"
|
||||||
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||||
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||||
|
"email" = "ایمیل"
|
||||||
|
"emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||||
"IPLimit" = "محدودیت ای پی"
|
"IPLimit" = "محدودیت ای پی"
|
||||||
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||||
"Email" = "ایمیل"
|
|
||||||
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
|
||||||
"IPLimitlog" = "گزارش ها"
|
"IPLimitlog" = "گزارش ها"
|
||||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"XTLSdec" = "هسته Xray باید 1.7.5 باشد"
|
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
||||||
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||||
|
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot)"
|
||||||
|
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
@@ -197,93 +206,40 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "رمزنگاری"
|
"encryption" = "رمزنگاری"
|
||||||
|
|
||||||
[pages.setting]
|
[pages.settings]
|
||||||
"title" = "تنظیمات"
|
"title" = "تنظیمات"
|
||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
|
"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
"actions" = "عملیات ها"
|
"actions" = "عملیات ها"
|
||||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelSettings" = "تنظیمات پنل"
|
||||||
"userSetting" = "تنظیمات مدیر"
|
"securitySettings" = "تنظیمات امنیتی"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
"TGReminder" = "تنظیمات ربات تلگرام"
|
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
||||||
"otherSetting" = "دیگر تنظیمات"
|
|
||||||
"panelListeningIP" = "محدودیت آی پی پنل"
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل"
|
||||||
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
||||||
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
|
||||||
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
||||||
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
|
||||||
"panelUrlPath" = "آدرس روت پنل"
|
"panelUrlPath" = "آدرس روت پنل"
|
||||||
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود"
|
||||||
"oldUsername" = "نام کاربری فعلی"
|
"oldUsername" = "نام کاربری فعلی"
|
||||||
"currentPassword" = "رمز عبور فعلی"
|
"currentPassword" = "رمز عبور فعلی"
|
||||||
"newUsername" = "نام کاربری جدید"
|
"newUsername" = "نام کاربری جدید"
|
||||||
"newPassword" = "رمز عبور جدید"
|
"newPassword" = "رمز عبور جدید"
|
||||||
"basicTemplate" = "بخش پایه"
|
|
||||||
"advancedTemplate" = "بخش های پیشرفته الگو"
|
|
||||||
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
|
||||||
"generalConfigs" = "تنظیمات عمومی"
|
|
||||||
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
|
||||||
"countryConfigs" = "تنظیمات برای کشورها"
|
|
||||||
"countryConfigsDesc" = "این گزینه از اتصال کاربران به دامنه های کشوری خاص جلوگیری می کند."
|
|
||||||
"ipv4Configs" = "تنظیمات برای IPv4"
|
|
||||||
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن 4 به دامنه های هدف هدایت می شود."
|
|
||||||
"warpConfigs" = "تنظیمات برای WARP"
|
|
||||||
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند."
|
|
||||||
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
|
||||||
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
|
||||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
|
||||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
|
||||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
|
||||||
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
|
||||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
|
||||||
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
|
||||||
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
|
||||||
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
|
||||||
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
|
||||||
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
|
||||||
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
|
||||||
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
|
|
||||||
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
|
|
||||||
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
|
|
||||||
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
|
|
||||||
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
|
|
||||||
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigInbounds" = "تنظیمات ورودی"
|
|
||||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
|
||||||
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
|
||||||
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
||||||
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramBotEnableDesc" = "از طریق بات تلگرام به امکانات این پنل متصل شوید"
|
||||||
"telegramToken" = "توکن تلگرام"
|
"telegramToken" = "توکن تلگرام"
|
||||||
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather"
|
||||||
"telegramChatId" = "آی دی تلگرام مدیریت"
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramChatIdDesc" = "از @userinfobot برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. "
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید "
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
@@ -294,16 +250,235 @@
|
|||||||
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
||||||
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZonee" = "منظقه زمانی"
|
"timeZone" = "منظقه زمانی"
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
"subSettings" = "سابسکریپشن"
|
||||||
|
"subEnable" = "فعال کردن سرویس"
|
||||||
|
"subEnableDesc" = "ویژگی سابسکریپشن با پیکربندی جداگانه"
|
||||||
|
"subListen" = "محدودیت آیپی"
|
||||||
|
"subListenDesc" = "برای استفاده از همه آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subPort" = "پورت سرویس"
|
||||||
|
"subPortDesc" = "شماره پورت برای ارائه خدمات سابسکریپشن باید خالی باشد"
|
||||||
|
"subCertPath" = "مسیر فایل کلید عمومی گواهی سابسکریپشن"
|
||||||
|
"subCertPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subKeyPath" = "مسیر فایل کلید خصوصی گواهی سابسکریپشن"
|
||||||
|
"subKeyPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subPath" = "مسیر ریشه سابسکریپشن"
|
||||||
|
"subPathDesc" = "باید با '/' شروع شود و با '/' ختم شود."
|
||||||
|
"subDomain" = "دامنه مخصوص سابسکریپشن"
|
||||||
|
"subDomainDesc" = "برای نظارت بر همه دامنه ها و آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subUpdates" = "فاصله به روز رسانی های سابسکریپشن"
|
||||||
|
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||||
|
|
||||||
|
[pages.settings.templates]
|
||||||
|
"title" = "الگوها"
|
||||||
|
"basicTemplate" = "بخش الگو پایه"
|
||||||
|
"advancedTemplate" = "بخش الگو پیشرفته"
|
||||||
|
"completeTemplate" = "بخش الگو کامل"
|
||||||
|
"generalConfigs" = "تنظیمات عمومی"
|
||||||
|
"generalConfigsDesc" = "این تنظیمات میتواند ترافیک کلی سرویس را متاثر کند"
|
||||||
|
"blockConfigs" = "مسدود سازی"
|
||||||
|
"blockConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند"
|
||||||
|
"blockCountryConfigs" = "تنظیمات برای مسدودسازی کشورها"
|
||||||
|
"blockCountryConfigsDesc" = "این گزینه اتصال کاربران به دامنه های کشوری خاص را مسدود می کند"
|
||||||
|
"directCountryConfigs" = "تنظیمات برای اتصال مستقیم کشورها"
|
||||||
|
"directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند"
|
||||||
|
"ipv4Configs" = "تنظیمات برای IPv4"
|
||||||
|
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود"
|
||||||
|
"warpConfigs" = "تنظیمات برای WARP"
|
||||||
|
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند"
|
||||||
|
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
||||||
|
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!"
|
||||||
|
"xrayConfigFreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم"
|
||||||
|
"xrayConfigFreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم"
|
||||||
|
"xrayConfigRoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی"
|
||||||
|
"xrayConfigRoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه"
|
||||||
|
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
||||||
|
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد"
|
||||||
|
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
||||||
|
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد"
|
||||||
|
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
||||||
|
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد"
|
||||||
|
"xrayConfigFamily" = "فعال کردن حالت خانواده"
|
||||||
|
"xrayConfigFamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن"
|
||||||
|
"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
|
||||||
|
"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد"
|
||||||
|
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||||
|
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد"
|
||||||
|
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
|
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد"
|
||||||
|
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
||||||
|
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد"
|
||||||
|
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
||||||
|
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد"
|
||||||
|
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
||||||
|
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigDirectIRIp" = "ارتباط مستقیم به آیپی های ایران"
|
||||||
|
"xrayConfigDirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد"
|
||||||
|
"xrayConfigDirectIRDomain" = "ارتباط مستقیم به دامنه های ایران"
|
||||||
|
"xrayConfigDirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد"
|
||||||
|
"xrayConfigDirectChinaIp" = "ارتباط مستقیم به آیپی های چین"
|
||||||
|
"xrayConfigDirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد"
|
||||||
|
"xrayConfigDirectChinaDomain" = "ارتباط مستقیم به دامنه های چین"
|
||||||
|
"xrayConfigDirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد"
|
||||||
|
"xrayConfigDirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه"
|
||||||
|
"xrayConfigDirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigDirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه"
|
||||||
|
"xrayConfigDirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند"
|
||||||
|
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند"
|
||||||
|
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
|
||||||
|
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند"
|
||||||
|
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند"
|
||||||
|
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
|
||||||
|
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند"
|
||||||
|
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند"
|
||||||
|
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
|
||||||
|
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند"
|
||||||
|
"xrayConfigInbounds" = "تنظیمات ورودی"
|
||||||
|
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید"
|
||||||
|
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||||
|
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید"
|
||||||
|
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
||||||
|
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید"
|
||||||
|
"manualLists" = "لیست های دستی"
|
||||||
|
"manualListsDesc" = "فرمت: JSON Array"
|
||||||
|
"manualBlockedIPs" = "لیست آیپی های مسدود شده"
|
||||||
|
"manualBlockedDomains" = "لیست دامنه های مسدود شده"
|
||||||
|
"manualDirectIPs" = "لیست آیپی های مستقیم"
|
||||||
|
"manualDirectDomains" = "لیست دامنه های مستقیم"
|
||||||
|
"manualIPv4Domains" = "لیست دامنههای IPv4"
|
||||||
|
"manualWARPDomains" = "لیست دامنه های WARP"
|
||||||
|
|
||||||
|
[pages.settings.security]
|
||||||
|
"admin" = "مدیر"
|
||||||
|
"secret" = "توکن امنیتی"
|
||||||
"loginSecurity" = "لاگین ایمن"
|
"loginSecurity" = "لاگین ایمن"
|
||||||
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
|
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
|
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySetting" = "ویرایش تنظیمات"
|
"modifySettings" = "ویرایش تنظیمات"
|
||||||
"getSetting" = "دریافت تنظیمات"
|
"getSettings" = "دریافت تنظیمات"
|
||||||
"modifyUser" = "ویرایش کاربر"
|
"modifyUser" = "ویرایش کاربر"
|
||||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
|
||||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
||||||
|
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||||
|
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
||||||
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
|
"unlimited" = "♾ نامحدود"
|
||||||
|
"month" = "ماه"
|
||||||
|
"months" = "ماهها"
|
||||||
|
"day" = "روز"
|
||||||
|
"days" = "روزها"
|
||||||
|
"unknown" = "نامشخص"
|
||||||
|
"inbounds" = "ورودیها"
|
||||||
|
"clients" = "کلاینتها"
|
||||||
|
|
||||||
|
[tgbot.commands]
|
||||||
|
"unknown" = "❗ دستور ناشناخته"
|
||||||
|
"pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n"
|
||||||
|
"help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه دادههای خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را میدهد.\r\n\r\n"
|
||||||
|
"start" = "👋 سلام <i>{{ .Firstname }}</i>.\r\n"
|
||||||
|
"welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
|
||||||
|
"status" = "✅ ربات در حالت عادی است!"
|
||||||
|
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
||||||
|
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
|
||||||
|
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
|
||||||
|
|
||||||
|
[tgbot.messages]
|
||||||
|
"cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است."
|
||||||
|
"selectUserFailed" = "❌ خطا در انتخاب کاربر!"
|
||||||
|
"userSaved" = "✅ کاربر تلگرام ذخیره شد."
|
||||||
|
"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
|
||||||
|
"loginFailed" = "❗️ ورود به پنل ناموفق بود.\r\n"
|
||||||
|
"report" = "🕰 گزارشات زمانبندی شده: {{ .RunTime }}\r\n"
|
||||||
|
"datetime" = "⏰ تاریخ-زمان: {{ .DateTime }}\r\n"
|
||||||
|
"hostname" = "💻 نام میزبان: {{ .Hostname }}\r\n"
|
||||||
|
"version" = "🚀 نسخه X-UI: {{ .Version }}\r\n"
|
||||||
|
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||||
|
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||||
|
"ip" = "🌐 آدرس IP: {{ .IP }}\r\n"
|
||||||
|
"ips" = "🔢 آدرسهای IP: \r\n{{ .IPs }}\r\n"
|
||||||
|
"serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||||
|
"serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||||
|
"serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n"
|
||||||
|
"tcpCount" = "🔹 تعداد ترافیک TCP: {{ .Count }}\r\n"
|
||||||
|
"udpCount" = "🔸 تعداد ترافیک UDP: {{ .Count }}\r\n"
|
||||||
|
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||||
|
"xrayStatus" = "ℹ️ وضعیت Xray: {{ .State }}\r\n"
|
||||||
|
"username" = "👤 نام کاربری: {{ .Username }}\r\n"
|
||||||
|
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
||||||
|
"inbound" = "📍 ورودی: {{ .Remark }}\r\n"
|
||||||
|
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
||||||
|
"expire" = "📅 تاریخ انقضا: {{ .DateTime }}\r\n \r\n"
|
||||||
|
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n \r\n"
|
||||||
|
"active" = "💡 فعال: {{ .Enable }}\r\n"
|
||||||
|
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
||||||
|
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
||||||
|
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
||||||
|
"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
|
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||||
|
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "🔄🕒 تازهسازی شده در: {{ .Time }}\r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ بستن کیبورد"
|
||||||
|
"cancel" = "❌ لغو"
|
||||||
|
"cancelReset" = "❌ لغو تنظیم مجدد"
|
||||||
|
"cancelIpLimit" = "❌ لغو محدودیت IP"
|
||||||
|
"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟"
|
||||||
|
"confirmClearIps" = "✅ تأیید پاکسازی آدرسهای IP؟"
|
||||||
|
"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟"
|
||||||
|
"dbBackup" = "دریافت پشتیبان پایگاه داده"
|
||||||
|
"serverUsage" = "استفاده از سرور"
|
||||||
|
"getInbounds" = "دریافت ورودیها"
|
||||||
|
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
||||||
|
"clientUsage" = "دریافت آمار کاربر"
|
||||||
|
"commands" = "دستورات"
|
||||||
|
"refresh" = "🔄 تازهسازی"
|
||||||
|
"clearIPs" = "❌ پاکسازی آدرسها"
|
||||||
|
"removeTGUser" = "❌ حذف کاربر تلگرام"
|
||||||
|
"selectTGUser" = "👤 انتخاب کاربر تلگرام"
|
||||||
|
"selectOneTGUser" = "👤 یک کاربر تلگرام را انتخاب کنید:"
|
||||||
|
"resetTraffic" = "📈 تنظیم مجدد ترافیک"
|
||||||
|
"resetExpire" = "📅 تنظیم مجدد تاریخ انقضا"
|
||||||
|
"ipLog" = "🔢 لاگ آدرسهای IP"
|
||||||
|
"ipLimit" = "🔢 محدودیت IP"
|
||||||
|
"setTGUser" = "👤 تنظیم کاربر تلگرام"
|
||||||
|
"toggle" = "🔘 فعال / غیرفعال"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ خطا در عملیات."
|
||||||
|
"getInboundsFailed" = "❌ دریافت ورودیها با خطا مواجه شد."
|
||||||
|
"canceled" = "❌ {{ .Email }} : عملیات لغو شد."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت تازهسازی شدند."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت پاکسازی شدند."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : دریافت لاگ آدرسهای IP."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد."
|
||||||
|
"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که نام کاربری یا شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
|||||||