Compare commits
383 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
329889ec00 | ||
|
|
95268dbc61 | ||
|
|
9a3200c9b5 | ||
|
|
c213fb6216 | ||
|
|
dd0217b46b | ||
|
|
b805bf6222 | ||
|
|
07b9474212 | ||
|
|
bf971911d5 | ||
|
|
c46ced0c4c | ||
|
|
dd3bbbc4af | ||
|
|
a97f90d225 | ||
|
|
6066edd510 | ||
|
|
eaec9e54ad | ||
|
|
fafcb2e8e7 | ||
|
|
145ea1e6f1 | ||
|
|
4cfed17650 | ||
|
|
c2b1fb4855 | ||
|
|
09807b39aa | ||
|
|
a0ec2f3972 | ||
|
|
e4b1dc20c3 | ||
|
|
2f05d4960e | ||
|
|
e63d2644bd | ||
|
|
56e4d13179 | ||
|
|
0dd0ba717f | ||
|
|
8050330e2e | ||
|
|
65e35c1711 | ||
|
|
0311ae4d05 | ||
|
|
6f09fae28b | ||
|
|
c2e9ee3665 | ||
|
|
1f78842b70 | ||
|
|
81a057d638 | ||
|
|
64d17bee70 | ||
|
|
6beb19b945 | ||
|
|
6b84d39700 | ||
|
|
5f3b91f3f1 | ||
|
|
9e433ea4c4 | ||
|
|
39537f6f44 | ||
|
|
44b34c437e | ||
|
|
467c5f807e | ||
|
|
1028319386 | ||
|
|
f726474a5d | ||
|
|
cd7a790637 | ||
|
|
4f74f5154b | ||
|
|
6e22aa59e7 | ||
|
|
85df1301dc | ||
|
|
e92aba0179 | ||
|
|
85fb2fda5e | ||
|
|
f57e693023 | ||
|
|
83f6f13b50 | ||
|
|
b833ed7992 | ||
|
|
5188d516e3 | ||
|
|
97925eeebe | ||
|
|
1328bb5aba | ||
|
|
4cc755c883 | ||
|
|
4e89c71095 | ||
|
|
d40e61fc45 | ||
|
|
c0f1a926e5 | ||
|
|
970dd0915e | ||
|
|
526e2578b0 | ||
|
|
ad9134bc1a | ||
|
|
b5657ab87d | ||
|
|
2c7ec3dc8a | ||
|
|
0fe35fde49 | ||
|
|
c7658973c3 | ||
|
|
dea61b839c | ||
|
|
9063336778 | ||
|
|
7fc3d37851 | ||
|
|
81aa3ed10e | ||
|
|
ca10623e40 | ||
|
|
547e38079f | ||
|
|
29e40a0bce | ||
|
|
4c1fa59453 | ||
|
|
84b0471ca0 | ||
|
|
1c785b930f | ||
|
|
c8ea9e43ec | ||
|
|
594d682e20 | ||
|
|
70f250dfe1 | ||
|
|
1030bcf321 | ||
|
|
fdc1124ea4 | ||
|
|
33a598366b | ||
|
|
459c19eee2 | ||
|
|
d73a995257 | ||
|
|
e4e0deeed4 | ||
|
|
48dce38e14 | ||
|
|
3eebd7c3e5 | ||
|
|
2fa151d52e | ||
|
|
c182f48079 | ||
|
|
94fad02737 | ||
|
|
d694e6eafc | ||
|
|
2a9cb6d29e | ||
|
|
c565a429af | ||
|
|
572d912858 | ||
|
|
6ae80fc992 | ||
|
|
ef7b979d53 | ||
|
|
b203067dfd | ||
|
|
1c9fc9422e | ||
|
|
6c26e40aea | ||
|
|
fe963d91ef | ||
|
|
c9461f1647 | ||
|
|
ea7fe09c27 | ||
|
|
8170b65db4 | ||
|
|
a2d8c98b0d | ||
|
|
8442836512 | ||
|
|
331f2adb1c | ||
|
|
45a8d6c95e | ||
|
|
1f2eb2ca1a | ||
|
|
de571a2da4 | ||
|
|
2c233dffa5 | ||
|
|
31339d6bf8 | ||
|
|
8865443438 | ||
|
|
835deb77f4 | ||
|
|
6a7c3716ac | ||
|
|
15211f81b1 | ||
|
|
b3f7a6572e | ||
|
|
6f28a3a2fe | ||
|
|
896cc5386c | ||
|
|
76f70ce1e9 | ||
|
|
6aa3c8d4a2 | ||
|
|
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.5.3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.2.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.9.1
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2.2.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4.6.0
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v4.1.1
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64, linux/arm64/v8
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
124
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release X-ui
|
name: Release 3X-ui
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -6,85 +7,70 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linuxamd64build:
|
build:
|
||||||
name: build x-ui amd64 version
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [amd64, arm64]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- name: Checkout repository
|
||||||
- name: Set up Go
|
uses: actions/checkout@v3.5.3
|
||||||
uses: actions/setup-go@v4.0.0
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4.0.1
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: 'stable'
|
||||||
- name: build linux amd64 version
|
|
||||||
run: |
|
- name: Install dependencies for arm64
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
if: matrix.platform == 'arm64'
|
||||||
mkdir x-ui
|
|
||||||
cp xui-release x-ui/xui-release
|
|
||||||
cp x-ui.service x-ui/x-ui.service
|
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
|
||||||
cd x-ui
|
|
||||||
mv xui-release x-ui
|
|
||||||
mkdir bin
|
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
|
||||||
unzip Xray-linux-64.zip
|
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
|
||||||
mv xray xray-linux-amd64
|
|
||||||
cd ..
|
|
||||||
cd ..
|
|
||||||
- name: package
|
|
||||||
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
|
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.5.0
|
|
||||||
with:
|
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: ${{ github.ref }}
|
|
||||||
file: x-ui-linux-amd64.tar.gz
|
|
||||||
asset_name: x-ui-linux-amd64.tar.gz
|
|
||||||
prerelease: true
|
|
||||||
overwrite: true
|
|
||||||
linuxarm64build:
|
|
||||||
name: build x-ui arm64 version
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v4.0.0
|
|
||||||
with:
|
|
||||||
go-version: "stable"
|
|
||||||
- name: build linux arm64 version
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o xui-release -v main.go
|
|
||||||
|
- name: Build x-ui
|
||||||
|
run: |
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=${{ matrix.platform }}
|
||||||
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
|
export CC=aarch64-linux-gnu-gcc
|
||||||
|
fi
|
||||||
|
go build -o xui-release -v main.go
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/xui-release
|
cp xui-release x-ui/
|
||||||
cp x-ui.service x-ui/x-ui.service
|
cp x-ui.service x-ui/
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
cp x-ui.sh x-ui/
|
||||||
cd x-ui
|
mv x-ui/xui-release x-ui/x-ui
|
||||||
mv xui-release x-ui
|
mkdir x-ui/bin
|
||||||
mkdir bin
|
cd x-ui/bin
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
# Download dependencies
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-64.zip
|
||||||
|
unzip Xray-linux-64.zip
|
||||||
|
rm -f Xray-linux-64.zip
|
||||||
|
else
|
||||||
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-arm64-v8a.zip
|
||||||
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
|
rm -f Xray-linux-arm64-v8a.zip
|
||||||
|
fi
|
||||||
|
rm -f geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
mv xray xray-linux-arm64
|
mv xray xray-linux-${{ matrix.platform }}
|
||||||
cd ..
|
cd ../..
|
||||||
cd ..
|
|
||||||
- name: package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.5.0
|
- name: Upload
|
||||||
|
uses: svenstaro/upload-release-action@2.7.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-arm64.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-arm64.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
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
|
|
||||||
|
|||||||
7
DockerEntrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Start fail2ban
|
||||||
|
fail2ban-client -x start
|
||||||
|
|
||||||
|
# Run x-ui
|
||||||
|
exec /app/x-ui
|
||||||
28
DockerInit.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
amd64)
|
||||||
|
ARCH="64"
|
||||||
|
FNAME="amd64"
|
||||||
|
;;
|
||||||
|
arm64)
|
||||||
|
ARCH="arm64-v8a"
|
||||||
|
FNAME="arm64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARCH="64"
|
||||||
|
FNAME="amd64"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p build/bin
|
||||||
|
cd build/bin
|
||||||
|
|
||||||
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.1/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/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||||
49
Dockerfile
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# ========================================================
|
||||||
|
# Stage: Builder
|
||||||
|
# ========================================================
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETARCH
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
|
||||||
|
RUN apk --no-cache --update add \
|
||||||
|
build-base \
|
||||||
|
gcc \
|
||||||
|
wget \
|
||||||
|
unzip
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go build -o build/x-ui main.go
|
||||||
|
RUN ./DockerInit.sh "$TARGETARCH"
|
||||||
|
|
||||||
|
# ========================================================
|
||||||
|
# Stage: Final Image of 3x-ui
|
||||||
|
# ========================================================
|
||||||
|
FROM alpine
|
||||||
|
ENV TZ=Asia/Tehran
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache --update \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
fail2ban
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/ /app/
|
||||||
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
|
# Configure fail2ban
|
||||||
|
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||||
|
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/^\[sshd\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/#allowipv6 = auto/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf
|
||||||
|
|
||||||
|
RUN chmod +x \
|
||||||
|
/app/DockerEntrypoint.sh \
|
||||||
|
/app/x-ui \
|
||||||
|
/usr/bin/x-ui
|
||||||
|
|
||||||
|
VOLUME [ "/etc/x-ui" ]
|
||||||
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
297
README.md
@@ -1,16 +1,19 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -18,12 +21,12 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
|||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install custom version
|
# Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.3.3`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.7.1`:
|
||||||
|
|
||||||
```
|
```
|
||||||
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.7.1
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
@@ -33,73 +36,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)
|
|
||||||
|
|
||||||
|
You also can use `x-ui` menu then select `16. SSL Certificate Management`
|
||||||
# Default settings
|
|
||||||
|
|
||||||
- Port: 2053
|
|
||||||
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
|
||||||
- database path: /etc/x-ui/x-ui.db
|
|
||||||
- xray config path: /usr/local/x-ui/bin/config.json
|
|
||||||
|
|
||||||
Before you set ssl on settings
|
|
||||||
|
|
||||||
- http://ip:2053/xui
|
|
||||||
- http://domain:2053/xui
|
|
||||||
|
|
||||||
After you set ssl on settings
|
|
||||||
|
|
||||||
- https://yourdomain:2053/xui
|
|
||||||
|
|
||||||
# Environment Variables
|
|
||||||
|
|
||||||
| Variable | Type | Default |
|
|
||||||
| -------------- | :--------------------------------------------: | :------------ |
|
|
||||||
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
|
||||||
| XUI_DEBUG | `boolean` | `false` |
|
|
||||||
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
|
||||||
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
# Xray Configurations:
|
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
|
||||||
|
|
||||||
- [traffic](./media/configs/traffic.json)
|
|
||||||
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
|
||||||
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
|
||||||
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
|
||||||
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
|
||||||
|
|
||||||
If you want to use routing to WARP follow steps as below:
|
|
||||||
|
|
||||||
1. If you already installed warp, you can uninstall using below command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
warp u
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install WARP on **socks proxy mode**:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
Config Features:
|
|
||||||
|
|
||||||
- Block Ads
|
|
||||||
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
|
||||||
- Fix Google 403 error
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@@ -116,8 +54,168 @@ 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
|
# Manual Install & Upgrade
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Manual Install details</summary>
|
||||||
|
|
||||||
|
1. To download the latest version of the compressed package directly to your server, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
cd /root/
|
||||||
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||||
|
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||||
|
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||||
|
mv x-ui/ /usr/local/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl restart x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Install with Docker
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Docker details</summary>
|
||||||
|
|
||||||
|
1. Install Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Clone the Project Repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/MHSanaei/3x-ui.git
|
||||||
|
cd 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the Service
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Default settings
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Default settings details</summary>
|
||||||
|
|
||||||
|
- Port: 2053
|
||||||
|
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
||||||
|
- database path: /etc/x-ui/x-ui.db
|
||||||
|
- xray config path: /usr/local/x-ui/bin/config.json
|
||||||
|
|
||||||
|
Before you set ssl on settings
|
||||||
|
|
||||||
|
- http://ip:2053/panel
|
||||||
|
- http://domain:2053/panel
|
||||||
|
|
||||||
|
After you set ssl on settings
|
||||||
|
|
||||||
|
- https://yourdomain:2053/panel
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Xray Configurations:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Xray Configurations details</summary>
|
||||||
|
|
||||||
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
|
|
||||||
|
- [traffic](./media/configs/traffic.json)
|
||||||
|
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
||||||
|
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
||||||
|
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
||||||
|
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for WARP Configuration details</summary>
|
||||||
|
|
||||||
|
If you want to use routing to WARP follow steps as below:
|
||||||
|
|
||||||
|
1. If you already installed warp, you can uninstall using below command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
warp u
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install WARP on **socks proxy mode**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
Config Features:
|
||||||
|
|
||||||
|
- Block Ads
|
||||||
|
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
||||||
|
- Fix Google 403 error
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# IP Limit
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for IP Limit details</summary>
|
||||||
|
|
||||||
|
**Note: IP Limit won't work correctly when using IP Tunnel**
|
||||||
|
|
||||||
|
- For versions up to `v1.6.1`:
|
||||||
|
|
||||||
|
- IP limit is built-in into the panel.
|
||||||
|
|
||||||
|
- For versions `v1.7.0` and newer:
|
||||||
|
|
||||||
|
- To make IP Limit work properly, you need to install fail2ban and its required files by following these steps:
|
||||||
|
|
||||||
|
1. Use the `x-ui` command inside the shell.
|
||||||
|
2. Select `16. IP Limit Management`.
|
||||||
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Telegram Bot
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Telegram Bot details</summary>
|
||||||
|
|
||||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
Set the robot-related parameters in the panel background, including:
|
Set the robot-related parameters in the panel background, including:
|
||||||
@@ -146,38 +244,72 @@ Reference syntax:
|
|||||||
- CPU threshold notification
|
- CPU threshold notification
|
||||||
- Threshold for Expiration time and Traffic to report in advance
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
- Support client report menu if client's telegram username added to the user's configurations
|
- Support client report menu if client's telegram username added to the user's configurations
|
||||||
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Menu based bot
|
- Menu based bot
|
||||||
- Search client by email ( only admin )
|
- Search client by email ( only admin )
|
||||||
- Check all inbounds
|
- Check all inbounds
|
||||||
- Check server status
|
- Check server status
|
||||||
- Check depleted users
|
- Check depleted users
|
||||||
- Receive backup by request and in periodic reports
|
- Receive backup by request and in periodic reports
|
||||||
|
- Multi language bot
|
||||||
|
</details>
|
||||||
|
|
||||||
## API routes
|
# API routes
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
<details>
|
||||||
- `/xui/API/inbounds` base for following actions:
|
<summary>Click for API routes details</summary>
|
||||||
|
|
||||||
|
- `/login` with `POST` user data: `{username: '', password: ''}` for login
|
||||||
|
- `/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)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Environment Variables details</summary>
|
||||||
|
|
||||||
|
| Variable | Type | Default |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
|
|
||||||
@@ -190,17 +322,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
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ var name string
|
|||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Debug LogLevel = "debug"
|
Debug LogLevel = "debug"
|
||||||
Info LogLevel = "info"
|
Info LogLevel = "info"
|
||||||
Warn LogLevel = "warn"
|
Notice LogLevel = "notice"
|
||||||
Error LogLevel = "error"
|
Warn LogLevel = "warn"
|
||||||
|
Error LogLevel = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
@@ -64,3 +65,11 @@ func GetDBFolderPath() string {
|
|||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogFolder() string {
|
||||||
|
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||||
|
if logFolderPath == "" {
|
||||||
|
logFolderPath = "/var/log"
|
||||||
|
}
|
||||||
|
return logFolderPath
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.3.4
|
1.7.5
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
@@ -15,6 +18,14 @@ 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 {
|
||||||
@@ -43,16 +54,18 @@ func initInbound() error {
|
|||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initInboundClientIps() error {
|
func initInboundClientIps() error {
|
||||||
return db.AutoMigrate(&model.InboundClientIps{})
|
return db.AutoMigrate(&model.InboundClientIps{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initClientTraffic() error {
|
func initClientTraffic() error {
|
||||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +86,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 +102,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
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ type Client struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
AlterIds uint16 `json:"alterId"`
|
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
LimitIP int `json:"limitIp"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
|
|||||||
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
|
||||||
94
go.mod
@@ -3,61 +3,95 @@ module x-ui
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/Workiva/go-datastructures v1.1.0
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.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.26.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.9
|
||||||
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.7
|
||||||
github.com/xtls/xray-core v1.8.1
|
github.com/xtls/xray-core v1.8.3
|
||||||
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.11.0
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.57.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.2
|
||||||
gorm.io/gorm v1.25.0
|
gorm.io/gorm v1.25.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/bytedance/sonic v1.8.8 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/bytedance/sonic v1.10.0-rc // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
|
github.com/fasthttp/router v1.4.20 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.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.1 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // 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
|
||||||
github.com/gorilla/sessions v1.2.1 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
github.com/leodido/go-urn v1.2.3 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // 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.17 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.11.0 // 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/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.35.1 // indirect
|
||||||
|
github.com/refraction-networking/utls v1.3.3 // indirect
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
|
github.com/sagernet/sing v0.2.7 // indirect
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.2 // indirect
|
||||||
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
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.1 // 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/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.8.0 // indirect
|
github.com/valyala/fasthttp v1.48.0 // indirect
|
||||||
golang.org/x/net v0.9.0 // indirect
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||||
|
golang.org/x/mod v0.11.0 // indirect
|
||||||
|
golang.org/x/net v0.13.0 // indirect
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
golang.org/x/tools v0.10.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||||
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
403
go.sum
@@ -1,260 +1,439 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
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.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
|
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
||||||
|
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/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
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.10.0-rc h1:3S5HeWxjX08CUqNrXtEittExpJsEKBNzrV5UnrzHxVQ=
|
||||||
github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
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/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/fasthttp/router v1.4.20 h1:yPeNxz5WxZGojzolKqiP15DTXnxZce9Drv577GBrDgU=
|
||||||
|
github.com/fasthttp/router v1.4.20/go.mod h1:um867yNQKtERxBm+C+yzgWxjspTiQoA8z86Ec3fK/tc=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
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.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||||
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
|
||||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
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.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
|
||||||
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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
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/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=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||||
|
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
|
github.com/klauspost/compress v1.16.7/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.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-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/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mymmrac/telego v0.26.0 h1:m4B3SW9dxL4uHpyjBnmhQeiFO7GWCxFjsUKUvFx3mf0=
|
||||||
|
github.com/mymmrac/telego v0.26.0/go.mod h1:kizipjY3MhxmkcGvyz8jiw/26vEKAhR2V7YTE69iqvw=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/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.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||||
|
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
|
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
|
||||||
|
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||||
|
github.com/refraction-networking/utls v1.3.3 h1:f/TBLX7KBciRyFH3bwupp+CE4fzoYKCirhdRcC490sw=
|
||||||
|
github.com/refraction-networking/utls v1.3.3/go.mod h1:DlecWW1LMlMJu+9qpzzQqdHDT/C2LAe03EdpLUz/RL8=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/sagernet/sing v0.2.7 h1:cOy0FfPS8q7m0aJ51wS7LRQAGc9wF+fWhHtBDj99wy8=
|
||||||
|
github.com/sagernet/sing v0.2.7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.2 h1:ezSdVhrmIcwDXmCZF3bOJVMuVtTQWpda+1Op+Ie2TA4=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.2/go.mod h1:JIBWG6a7orB2HxBxYElViQFLUQxFVG7DuqIj8gD7uCQ=
|
||||||
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/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||||
|
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/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
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.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||||
|
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||||
|
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||||
|
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||||
|
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||||
|
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||||
|
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||||
|
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||||
|
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||||
|
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||||
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
||||||
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/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
|
||||||
|
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 h1:AMyzgjkh54WocjQSlCnT1LhDc/BKiUqtNOv40AkpURs=
|
||||||
|
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
||||||
|
github.com/xtls/xray-core v1.8.3 h1:lxaVklPjLKqUU4ua4qH8SBaRcAaNHlH+LmXOx0U/Ejg=
|
||||||
|
github.com/xtls/xray-core v1.8.3/go.mod h1:i7t4JFnq828P2+XK0XjGQ8W9x78iu+EJ7jI4l3sonIw=
|
||||||
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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
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=
|
||||||
|
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
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.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||||
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
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.4.2/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=
|
||||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
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.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-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-20210220032951-036812b2e83c/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=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.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=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||||
|
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.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/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||||
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||||
|
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||||
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
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.0-20200605160147-a5ece683394c/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.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
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=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||||
|
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
|||||||
30
install.sh
@@ -8,7 +8,7 @@ plain='\033[0m'
|
|||||||
cur_dir=$(pwd)
|
cur_dir=$(pwd)
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error:${plain} Please run this script with root privilege \n " && exit 1
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
||||||
|
|
||||||
# Check OS and set release variable
|
# Check OS and set release variable
|
||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
@@ -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,14 +39,14 @@ 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
|
||||||
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -1,26 +1,47 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var logger *logging.Logger
|
||||||
|
var logBuffer []struct {
|
||||||
|
time string
|
||||||
|
level logging.Level
|
||||||
|
log string
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
format := logging.MustStringFormatter(
|
|
||||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
|
||||||
)
|
|
||||||
newLogger := logging.MustGetLogger("x-ui")
|
newLogger := logging.MustGetLogger("x-ui")
|
||||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
var err error
|
||||||
|
var backend logging.Backend
|
||||||
|
var format logging.Formatter
|
||||||
|
ppid := os.Getppid()
|
||||||
|
|
||||||
|
if ppid == 1 {
|
||||||
|
backend, err = logging.NewSyslogBackend("")
|
||||||
|
format = logging.MustStringFormatter(
|
||||||
|
`%{level} - %{message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil || ppid != 1 {
|
||||||
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
format = logging.MustStringFormatter(
|
||||||
|
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||||
backendLeveled.SetLevel(level, "")
|
backendLeveled.SetLevel(level, "x-ui")
|
||||||
newLogger.SetBackend(backendLeveled)
|
newLogger.SetBackend(backendLeveled)
|
||||||
|
|
||||||
logger = newLogger
|
logger = newLogger
|
||||||
@@ -28,32 +49,70 @@ func InitLogger(level logging.Level) {
|
|||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
logger.Debugf(format, args...)
|
logger.Debugf(format, args...)
|
||||||
|
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(args ...interface{}) {
|
func Info(args ...interface{}) {
|
||||||
logger.Info(args...)
|
logger.Info(args...)
|
||||||
|
addToBuffer("INFO", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...interface{}) {
|
||||||
logger.Infof(format, args...)
|
logger.Infof(format, args...)
|
||||||
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...interface{}) {
|
||||||
logger.Warningf(format, args...)
|
logger.Warningf(format, args...)
|
||||||
|
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(args ...interface{}) {
|
func Error(args ...interface{}) {
|
||||||
logger.Error(args...)
|
logger.Error(args...)
|
||||||
|
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...interface{}) {
|
||||||
logger.Errorf(format, args...)
|
logger.Errorf(format, args...)
|
||||||
|
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToBuffer(level string, newLog string) {
|
||||||
|
t := time.Now()
|
||||||
|
if len(logBuffer) >= 10240 {
|
||||||
|
logBuffer = logBuffer[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevel, _ := logging.LogLevel(level)
|
||||||
|
logBuffer = append(logBuffer, struct {
|
||||||
|
time string
|
||||||
|
level logging.Level
|
||||||
|
log string
|
||||||
|
}{
|
||||||
|
time: t.Format("2006/01/02 15:04:05"),
|
||||||
|
level: logLevel,
|
||||||
|
log: newLog,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogs(c int, level string) []string {
|
||||||
|
var output []string
|
||||||
|
logLevel, _ := logging.LogLevel(level)
|
||||||
|
|
||||||
|
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
|
||||||
|
if logBuffer[i].level <= logLevel {
|
||||||
|
output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|||||||
52
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/v2ui"
|
"x-ui/sub"
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -27,6 +27,8 @@ func runWebServer() {
|
|||||||
logger.InitLogger(logging.DEBUG)
|
logger.InitLogger(logging.DEBUG)
|
||||||
case config.Info:
|
case config.Info:
|
||||||
logger.InitLogger(logging.INFO)
|
logger.InitLogger(logging.INFO)
|
||||||
|
case config.Notice:
|
||||||
|
logger.InitLogger(logging.NOTICE)
|
||||||
case config.Warn:
|
case config.Warn:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
@@ -50,6 +52,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 +74,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 +86,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 +160,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 +238,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!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,10 +271,6 @@ func main() {
|
|||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
|
||||||
var dbPath string
|
|
||||||
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
@@ -277,7 +298,6 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" v2-ui migrate form v2-ui")
|
|
||||||
fmt.Println(" migrate migrate form other/old x-ui")
|
fmt.Println(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
@@ -298,16 +318,6 @@ func main() {
|
|||||||
runWebServer()
|
runWebServer()
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
migrateDb()
|
||||||
case "v2-ui":
|
|
||||||
err := v2uiCmd.Parse(os.Args[2:])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = v2ui.MigrateFromV2UI(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("migrate from v2-ui failed:", err)
|
|
||||||
}
|
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -332,12 +342,10 @@ func main() {
|
|||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
v2uiCmd.Usage()
|
|
||||||
fmt.Println()
|
|
||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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": {}
|
||||||
}
|
}
|
||||||
162
sub/sub.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/middleware"
|
||||||
|
"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 != "" {
|
||||||
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
|
}
|
||||||
|
|
||||||
|
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,45 +1,63 @@
|
|||||||
package service
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"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, client.ExpiryTime)
|
||||||
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 +84,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,25 +110,52 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
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, expiryTime int64) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
return s.genVmessLink(inbound, email)
|
return s.genVmessLink(inbound, email, expiryTime)
|
||||||
case "vless":
|
case "vless":
|
||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email, expiryTime)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email, expiryTime)
|
||||||
|
case "shadowsocks":
|
||||||
|
return s.genShadowsocksLink(inbound, email, expiryTime)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remainedTraffic := s.getRemainedTraffic(email)
|
||||||
|
expiryTimeString := getExpiryTime(expiryTime)
|
||||||
|
remark := ""
|
||||||
|
isTerminated := strings.Contains(expiryTimeString, "Terminated") || strings.Contains(remainedTraffic, "Terminated")
|
||||||
|
|
||||||
|
if isTerminated {
|
||||||
|
remark = fmt.Sprintf("%s: %s⛔️", email, "Terminated")
|
||||||
|
} else {
|
||||||
|
remark = fmt.Sprintf("%s: %s - %s", email, remainedTraffic, expiryTimeString)
|
||||||
|
}
|
||||||
|
|
||||||
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 +207,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 +230,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 +240,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 {
|
||||||
@@ -197,20 +249,34 @@ 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
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVlessLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||||
address := s.address
|
address := s.address
|
||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
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 +332,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 +357,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 +393,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 +432,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 {
|
||||||
@@ -369,6 +447,10 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
@@ -380,18 +462,42 @@ 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
|
remainedTraffic := s.getRemainedTraffic(email)
|
||||||
|
expiryTimeString := getExpiryTime(expiryTime)
|
||||||
|
remark := ""
|
||||||
|
isTerminated := strings.Contains(expiryTimeString, "Terminated") || strings.Contains(remainedTraffic, "Terminated")
|
||||||
|
|
||||||
|
if isTerminated {
|
||||||
|
remark = fmt.Sprintf("%s: %s⛔️", email, "Terminated")
|
||||||
|
} else {
|
||||||
|
remark = fmt.Sprintf("%s: %s - %s", email, remainedTraffic, expiryTimeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) genTrojanLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||||
address := s.address
|
address := s.address
|
||||||
if inbound.Protocol != model.Trojan {
|
if inbound.Protocol != model.Trojan {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
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 +553,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 +578,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)
|
||||||
@@ -491,7 +601,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"], _ = shortIds[0].(string)
|
||||||
}
|
}
|
||||||
@@ -500,6 +610,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 +649,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 {
|
||||||
@@ -546,6 +664,10 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
@@ -558,7 +680,127 @@ 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
|
remainedTraffic := s.getRemainedTraffic(email)
|
||||||
|
expiryTimeString := getExpiryTime(expiryTime)
|
||||||
|
remark := ""
|
||||||
|
isTerminated := strings.Contains(expiryTimeString, "Terminated") || strings.Contains(remainedTraffic, "Terminated")
|
||||||
|
|
||||||
|
if isTerminated {
|
||||||
|
remark = fmt.Sprintf("%s: %s⛔️", email, "Terminated")
|
||||||
|
} else {
|
||||||
|
remark = fmt.Sprintf("%s: %s - %s", email, remainedTraffic, expiryTimeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, expiryTime int64) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streamNetwork := stream["network"].(string)
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
|
switch streamNetwork {
|
||||||
|
case "tcp":
|
||||||
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ := header["type"].(string)
|
||||||
|
if typeStr == "http" {
|
||||||
|
request := header["request"].(map[string]interface{})
|
||||||
|
requestPath, _ := request["path"].([]interface{})
|
||||||
|
params["path"] = requestPath[0].(string)
|
||||||
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
params["headerType"] = "http"
|
||||||
|
}
|
||||||
|
case "kcp":
|
||||||
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
params["seed"] = kcp["seed"].(string)
|
||||||
|
case "ws":
|
||||||
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
|
params["path"] = ws["path"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
case "http":
|
||||||
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
|
params["path"] = http["path"].(string)
|
||||||
|
params["host"] = searchHost(http)
|
||||||
|
case "quic":
|
||||||
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
|
params["quicSecurity"] = quic["security"].(string)
|
||||||
|
params["key"] = quic["key"].(string)
|
||||||
|
header := quic["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
case "grpc":
|
||||||
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
if grpc["multiMode"].(bool) {
|
||||||
|
params["mode"] = "multi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||||
|
if method[0] == '2' {
|
||||||
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
|
}
|
||||||
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
remainedTraffic := s.getRemainedTraffic(email)
|
||||||
|
expiryTimeString := getExpiryTime(expiryTime)
|
||||||
|
remark := ""
|
||||||
|
isTerminated := strings.Contains(expiryTimeString, "Terminated") || strings.Contains(remainedTraffic, "Terminated")
|
||||||
|
|
||||||
|
if isTerminated {
|
||||||
|
remark = fmt.Sprintf("%s: %s⛔️", clients[clientIndex].Email, "Terminated")
|
||||||
|
} else {
|
||||||
|
remark = fmt.Sprintf("%s: %s - %s", clients[clientIndex].Email, remainedTraffic, expiryTimeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,3 +845,45 @@ func searchHost(headers interface{}) string {
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExpiryTime(expiryTime int64) string {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
expiryString := ""
|
||||||
|
|
||||||
|
timeDifference := expiryTime/1000 - now
|
||||||
|
isTerminated := timeDifference/3600 <= 0
|
||||||
|
|
||||||
|
if expiryTime == 0 {
|
||||||
|
expiryString = "♾ ⏳"
|
||||||
|
} else if timeDifference > 172800 {
|
||||||
|
expiryString = fmt.Sprintf("%d %s⏳", timeDifference/86400, "Days")
|
||||||
|
} else if expiryTime < 0 {
|
||||||
|
expiryString = fmt.Sprintf("%d %s⏳", expiryTime/-86400000, "Days")
|
||||||
|
} else if isTerminated {
|
||||||
|
expiryString = fmt.Sprintf("%s⛔️", "Terminated")
|
||||||
|
} else {
|
||||||
|
expiryString = fmt.Sprintf("%d %s⏳", timeDifference/3600, "Hours")
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiryString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getRemainedTraffic(email string) string {
|
||||||
|
traffic, err := s.inboundService.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remainedTraffic := ""
|
||||||
|
isTerminated := traffic.Total-(traffic.Up+traffic.Down) < 0
|
||||||
|
|
||||||
|
if traffic.Total == 0 {
|
||||||
|
remainedTraffic = "♾ 📊"
|
||||||
|
} else if isTerminated {
|
||||||
|
remainedTraffic = fmt.Sprintf("%s⛔️", "Terminated")
|
||||||
|
} else {
|
||||||
|
remainedTraffic = fmt.Sprintf("%s%s", common.FormatTraffic(traffic.Total-(traffic.Up+traffic.Down)), "📊")
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainedTraffic
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
28
v2ui/db.go
@@ -1,28 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var v2db *gorm.DB
|
|
||||||
|
|
||||||
func initDB(dbPath string) error {
|
|
||||||
c := &gorm.Config{
|
|
||||||
Logger: logger.Discard,
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getV2Inbounds() ([]*V2Inbound, error) {
|
|
||||||
inbounds := make([]*V2Inbound, 0)
|
|
||||||
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
|
||||||
return inbounds, err
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import "x-ui/database/model"
|
|
||||||
|
|
||||||
type V2Inbound struct {
|
|
||||||
Id int `gorm:"primaryKey;autoIncrement"`
|
|
||||||
Port int `gorm:"unique"`
|
|
||||||
Listen string
|
|
||||||
Protocol string
|
|
||||||
Settings string
|
|
||||||
StreamSettings string
|
|
||||||
Tag string `gorm:"unique"`
|
|
||||||
Sniffing string
|
|
||||||
Remark string
|
|
||||||
Up int64
|
|
||||||
Down int64
|
|
||||||
Enable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) TableName() string {
|
|
||||||
return "inbound"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
|
||||||
return &model.Inbound{
|
|
||||||
UserId: userId,
|
|
||||||
Up: i.Up,
|
|
||||||
Down: i.Down,
|
|
||||||
Total: 0,
|
|
||||||
Remark: i.Remark,
|
|
||||||
Enable: i.Enable,
|
|
||||||
ExpiryTime: 0,
|
|
||||||
Listen: i.Listen,
|
|
||||||
Port: i.Port,
|
|
||||||
Protocol: model.Protocol(i.Protocol),
|
|
||||||
Settings: i.Settings,
|
|
||||||
StreamSettings: i.StreamSettings,
|
|
||||||
Tag: i.Tag,
|
|
||||||
Sniffing: i.Sniffing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
v2ui/v2ui.go
@@ -1,51 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"x-ui/config"
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/web/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MigrateFromV2UI(dbPath string) error {
|
|
||||||
err := initDB(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init v2-ui database failed:", err)
|
|
||||||
}
|
|
||||||
err = database.InitDB(config.GetDBPath())
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init x-ui database failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v2Inbounds, err := getV2Inbounds()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get v2-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
if len(v2Inbounds) == 0 {
|
|
||||||
fmt.Println("migrate v2-ui inbounds success: 0")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
userService := service.UserService{}
|
|
||||||
user, err := userService.GetFirstUser()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get x-ui user failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds := make([]*model.Inbound, 0)
|
|
||||||
for _, v2inbound := range v2Inbounds {
|
|
||||||
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundService := service.InboundService{}
|
|
||||||
err = inboundService.AddInbounds(inbounds)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("add x-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
154
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,12 @@ 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-color: #0a7557;background-image: linear-gradient( 270deg, rgba(123, 199, 77, 0) 30%, #00ab80, rgba(123, 199, 77, 0) 100% );background-repeat: no-repeat;animation: ma-bg-move linear 6.6s infinite;/*background: linear-gradient(90deg,#009670 0,#026247 100%);*/color: #fff;border-radius: 0.5rem}
|
||||||
|
@-webkit-keyframes ma-bg-move {0% {background-position: -500px 0;}100% {background-position: 1000px 0;}}@keyframes ma-bg-move {0% {background-position: -500px 0;}50% {background-position: 1000px 0;}100% {background-position: 1000px 0;}}
|
||||||
.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 +1033,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 +1081,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:#0a7557}
|
||||||
|
/*.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#0a7557}*/
|
||||||
.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 +1237,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 +1265,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 +1358,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 +1391,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 +2439,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 +2465,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 +2481,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 +2540,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 +2550,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 +2560,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 +2596,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 +2607,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 +2617,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 +2628,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 +2945,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 +2980,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 +3217,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,8 +3231,8 @@ 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:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||||
.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}
|
||||||
.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}
|
.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}
|
||||||
@@ -3268,7 +3271,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 +3281,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 +3306,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 +3533,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 +3674,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 +3970,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 +3992,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 +4053,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 +4076,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 +4495,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 {
|
||||||
@@ -179,9 +202,52 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-card-dark:hover {
|
||||||
border-color: #e8e8e8;
|
/*border-color: #e8e8e8;
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
animation:light-shadow ease-in 3s infinite;*/
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
@keyframes light-shadow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
box-shadow: 0 1px 11px 2px rgb(154 175 238 / 70%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.ant-setting-textarea {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
min-height: 300px !important;
|
||||||
|
/*max-height: 800px !important;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark-box-nohover{
|
||||||
|
margin-top: .5rem;
|
||||||
|
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);
|
||||||
@@ -199,6 +265,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 +289,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 +318,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 +339,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 +356,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 +409,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 +438,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 +449,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 +481,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() {
|
||||||
@@ -168,6 +168,7 @@ class AllSetting {
|
|||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.webListen = "";
|
this.webListen = "";
|
||||||
|
this.webDomain = "";
|
||||||
this.webPort = 2053;
|
this.webPort = 2053;
|
||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
@@ -180,9 +181,19 @@ class AllSetting {
|
|||||||
this.tgBotChatId = "";
|
this.tgBotChatId = "";
|
||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
|
this.tgBotLoginNotify = 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";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const Protocols = {
|
|||||||
TROJAN: 'trojan',
|
TROJAN: 'trojan',
|
||||||
SHADOWSOCKS: 'shadowsocks',
|
SHADOWSOCKS: 'shadowsocks',
|
||||||
DOKODEMO: 'dokodemo-door',
|
DOKODEMO: 'dokodemo-door',
|
||||||
MTPROTO: 'mtproto',
|
|
||||||
SOCKS: 'socks',
|
SOCKS: 'socks',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
};
|
};
|
||||||
@@ -17,15 +16,13 @@ const VmessMethods = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
const XTLS_FLOW_CONTROL = {
|
||||||
@@ -88,6 +85,7 @@ const SNIFFING_OPTION = {
|
|||||||
HTTP: "http",
|
HTTP: "http",
|
||||||
TLS: "tls",
|
TLS: "tls",
|
||||||
QUIC: "quic",
|
QUIC: "quic",
|
||||||
|
FAKEDNS: "fakedns"
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
@@ -386,7 +384,10 @@ class WsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HttpStreamSettings extends XrayCommonClass {
|
class HttpStreamSettings extends XrayCommonClass {
|
||||||
constructor(path='/', host=['']) {
|
constructor(
|
||||||
|
path='/',
|
||||||
|
host=[''],
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host.length === 0 ? [''] : host;
|
this.host = host.length === 0 ? [''] : host;
|
||||||
@@ -418,9 +419,10 @@ class HttpStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class QuicStreamSettings extends XrayCommonClass {
|
class QuicStreamSettings extends XrayCommonClass {
|
||||||
constructor(security=VmessMethods.NONE,
|
constructor(security=VmessMethods.NONE,
|
||||||
key='', type='none') {
|
key=RandomUtil.randomSeq(10), type='none') {
|
||||||
super();
|
super();
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
@@ -449,7 +451,7 @@ class QuicStreamSettings extends XrayCommonClass {
|
|||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName="",
|
serviceName="",
|
||||||
multiMode=false
|
multiMode=false,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
@@ -466,16 +468,18 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
multiMode: this.multiMode
|
multiMode: this.multiMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
|
rejectUnknownSni = false,
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
settings=new TlsStreamSettings.Settings()) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
@@ -484,13 +488,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
|
this.rejectUnknownSni = rejectUnknownSni;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert() {
|
||||||
this.certs.push(cert);
|
this.certs.push(new TlsStreamSettings.Cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCert(index) {
|
removeCert(index) {
|
||||||
@@ -505,13 +510,14 @@ 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,
|
||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
|
json.rejectUnknownSni,
|
||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
settings,
|
settings,
|
||||||
@@ -524,6 +530,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
minVersion: this.minVersion,
|
minVersion: this.minVersion,
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
|
rejectUnknownSni: this.rejectUnknownSni,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
@@ -573,17 +580,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 +600,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 +616,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 +723,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 +742,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 +775,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,10 +795,32 @@ 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SockoptStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(
|
||||||
|
acceptProxyProtocol = false,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new SockoptStreamSettings(
|
||||||
|
json.acceptProxyProtocol,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
@@ -799,6 +833,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
sockopt = new SockoptStreamSettings(),
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
@@ -812,6 +847,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
@@ -851,6 +887,16 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSockopt() {
|
||||||
|
return ['http', 'grpc'].indexOf(this.network) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isSockopt(isSockopt) {
|
||||||
|
if (isSockopt) {
|
||||||
|
return ['http', 'grpc'].indexOf(this.network) !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
@@ -865,6 +911,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,12 +929,13 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
sockopt: this.isSockopt ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sniffing extends XrayCommonClass {
|
class Sniffing extends XrayCommonClass {
|
||||||
constructor(enabled=true, destOverride=['http', 'tls', 'quic']) {
|
constructor(enabled=true, destOverride=['http', 'tls', 'quic', 'fakedns']) {
|
||||||
super();
|
super();
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.destOverride = destOverride;
|
this.destOverride = destOverride;
|
||||||
@@ -897,7 +945,7 @@ class Sniffing extends XrayCommonClass {
|
|||||||
let destOverride = ObjectUtil.clone(json.destOverride);
|
let destOverride = ObjectUtil.clone(json.destOverride);
|
||||||
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
||||||
if (ObjectUtil.isEmpty(destOverride[0])) {
|
if (ObjectUtil.isEmpty(destOverride[0])) {
|
||||||
destOverride = ['http', 'tls', 'quic'];
|
destOverride = ['http', 'tls', 'quic', 'fakedns'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Sniffing(
|
return new Sniffing(
|
||||||
@@ -939,7 +987,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,7 +1015,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//for Reality
|
|
||||||
get reality() {
|
get reality() {
|
||||||
return this.stream.security === 'reality';
|
return this.stream.security === 'reality';
|
||||||
}
|
}
|
||||||
@@ -1012,66 +1059,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) {
|
||||||
@@ -1081,6 +1068,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get isSSMultiUser() {
|
||||||
|
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
||||||
|
}
|
||||||
|
get isSS2022(){
|
||||||
|
return this.method.substring(0,4) === "2022";
|
||||||
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
||||||
@@ -1146,9 +1139,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.length > 0 && 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 +1156,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;
|
||||||
@@ -1267,7 +1263,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
add: address,
|
add: address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: this.settings.vmesses[clientIndex].id,
|
||||||
aid: this.settings.vmesses[clientIndex].alterId,
|
|
||||||
net: this.stream.network,
|
net: this.stream.network,
|
||||||
type: 'none',
|
type: 'none',
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
@@ -1404,7 +1399,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1413,10 +1408,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.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1432,6 +1430,13 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `vless://${uuid}@${address}:${port}`;
|
const link = `vless://${uuid}@${address}:${port}`;
|
||||||
@@ -1443,13 +1448,71 @@ 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)) {
|
const type = this.stream.network;
|
||||||
address = server;
|
const params = new Map();
|
||||||
|
params.set("type", this.stream.network);
|
||||||
|
switch (type) {
|
||||||
|
case "tcp":
|
||||||
|
const tcp = this.stream.tcp;
|
||||||
|
if (tcp.type === 'http') {
|
||||||
|
const request = tcp.request;
|
||||||
|
params.set("path", request.path.join(','));
|
||||||
|
const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
const host = request.headers[index].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
|
params.set("headerType", 'http');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "kcp":
|
||||||
|
const kcp = this.stream.kcp;
|
||||||
|
params.set("headerType", kcp.type);
|
||||||
|
params.set("seed", kcp.seed);
|
||||||
|
break;
|
||||||
|
case "ws":
|
||||||
|
const ws = this.stream.ws;
|
||||||
|
params.set("path", ws.path);
|
||||||
|
const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
const host = ws.headers[index].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "http":
|
||||||
|
const http = this.stream.http;
|
||||||
|
params.set("path", http.path);
|
||||||
|
params.set("host", http.host);
|
||||||
|
break;
|
||||||
|
case "quic":
|
||||||
|
const quic = this.stream.quic;
|
||||||
|
params.set("quicSecurity", quic.security);
|
||||||
|
params.set("key", quic.key);
|
||||||
|
params.set("headerType", quic.type);
|
||||||
|
break;
|
||||||
|
case "grpc":
|
||||||
|
const grpc = this.stream.grpc;
|
||||||
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
if(grpc.multiMode){
|
||||||
|
params.set("mode", "multi");
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
|
||||||
|
let password = new Array();
|
||||||
|
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||||
|
if (this.isSS2022) password.push(settings.password);
|
||||||
|
|
||||||
|
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
||||||
|
const url = new URL(link);
|
||||||
|
for (const [key, value] of params) {
|
||||||
|
url.searchParams.set(key, value)
|
||||||
|
}
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1521,7 +1584,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1534,9 +1597,12 @@ 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) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1545,10 +1611,17 @@ 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)}`;
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +1632,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 +1650,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 '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1639,7 +1709,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
||||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
||||||
case Protocols.MTPROTO: return new Inbound.MtprotoSettings(protocol);
|
|
||||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1653,7 +1722,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
||||||
case Protocols.MTPROTO: return Inbound.MtprotoSettings.fromJson(json);
|
|
||||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1708,10 +1776,9 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
@@ -1724,7 +1791,6 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new Inbound.VmessSettings.Vmess(
|
return new Inbound.VmessSettings.Vmess(
|
||||||
json.id,
|
json.id,
|
||||||
json.alterId,
|
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
@@ -1800,7 +1866,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1923,7 +1989,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -2033,13 +2099,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 +2116,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 +2125,80 @@ 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(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
|
super();
|
||||||
|
this.method = method;
|
||||||
|
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 {
|
||||||
|
method: this.method,
|
||||||
|
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.method,
|
||||||
|
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);
|
||||||
@@ -2089,36 +2228,6 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.MtprotoSettings = class extends Inbound.Settings {
|
|
||||||
constructor(protocol, users=[new Inbound.MtprotoSettings.MtUser()]) {
|
|
||||||
super(protocol);
|
|
||||||
this.users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings(
|
|
||||||
Protocols.MTPROTO,
|
|
||||||
json.users.map(user => Inbound.MtprotoSettings.MtUser.fromJson(user)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
users: XrayCommonClass.toJsonArray(this.users),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Inbound.MtprotoSettings.MtUser = class extends XrayCommonClass {
|
|
||||||
constructor(secret=RandomUtil.randomMTSecret()) {
|
|
||||||
super();
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings.MtUser(json.secret);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
Inbound.SocksSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ const ONE_TB = ONE_GB * 1024;
|
|||||||
const ONE_PB = ONE_TB * 1024;
|
const ONE_PB = ONE_TB * 1024;
|
||||||
|
|
||||||
function sizeFormat(size) {
|
function sizeFormat(size) {
|
||||||
if (size < ONE_KB) {
|
if (size < 0) {
|
||||||
|
return "0 B";
|
||||||
|
} else if (size < ONE_KB) {
|
||||||
return size.toFixed(0) + " B";
|
return size.toFixed(0) + " B";
|
||||||
} else if (size < ONE_MB) {
|
} else if (size < ONE_MB) {
|
||||||
return (size / ONE_KB).toFixed(2) + " KB";
|
return (size / ONE_KB).toFixed(2) + " KB";
|
||||||
@@ -20,6 +22,23 @@ function sizeFormat(size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cpuSpeedFormat(speed) {
|
||||||
|
if (speed > 1000) {
|
||||||
|
const GHz = speed / 1000;
|
||||||
|
return GHz.toFixed(2) + " GHz";
|
||||||
|
} else {
|
||||||
|
return speed.toFixed(2) + " MHz";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cpuCoreFormat(cores) {
|
||||||
|
if (cores === 1) {
|
||||||
|
return "1 Core";
|
||||||
|
} else {
|
||||||
|
return cores + " Cores";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function base64(str) {
|
function base64(str) {
|
||||||
return Base64.encode(str);
|
return Base64.encode(str);
|
||||||
}
|
}
|
||||||
@@ -56,14 +75,81 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildURL({ host, port, isTLS, base, path }) {
|
||||||
|
if (!host || host.length === 0) host = window.location.hostname;
|
||||||
|
if (!port || port.length === 0) port = window.location.port;
|
||||||
|
|
||||||
|
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||||
|
|
||||||
|
const protocol = isTLS ? "https:" : "http:";
|
||||||
|
|
||||||
|
port = String(port);
|
||||||
|
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||||
|
port = "";
|
||||||
|
} else {
|
||||||
|
port = `:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${protocol}//${host}${port}${base}${path}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,18 @@ 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 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.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 = [
|
|
||||||
'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) {
|
||||||
@@ -112,63 +94,41 @@ class RandomUtil {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomShortIdSeq(count) {
|
static randomShortId() {
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < 8; ++i) {
|
||||||
str += shortIdSeq[this.randomInt(16)];
|
str += seq[this.randomInt(16)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomLowerAndNum(count) {
|
static randomLowerAndNum(len) {
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < len; ++i) {
|
||||||
str += seq[this.randomInt(36)];
|
str += seq[this.randomInt(36)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomMTSecret() {
|
|
||||||
let str = '';
|
|
||||||
for (let i = 0; i < 32; ++i) {
|
|
||||||
let index = this.randomInt(16);
|
|
||||||
if (index <= 9) {
|
|
||||||
str += index;
|
|
||||||
} else {
|
|
||||||
str += seq[index - 10];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomUUID() {
|
static randomUUID() {
|
||||||
let d = new Date().getTime();
|
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return template.replace(/[xy]/g, function (c) {
|
||||||
let r = (d + Math.random() * 16) % 16 | 0;
|
const randomValues = new Uint8Array(1);
|
||||||
d = Math.floor(d / 16);
|
crypto.getRandomValues(randomValues);
|
||||||
return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
let randomValue = randomValues[0] % 16;
|
||||||
|
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||||
|
return calculatedValue.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomText() {
|
static randomShadowsocksPassword() {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
let array = new Uint8Array(32);
|
||||||
var string = '';
|
window.crypto.getRandomValues(array);
|
||||||
var len = 6 + Math.floor(Math.random() * 5)
|
return btoa(String.fromCharCode.apply(null, array));
|
||||||
for(var ii=0; ii<len; ii++){
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randowShortId() {
|
|
||||||
let str = '';
|
|
||||||
str += this.randomShortIdSeq(8)
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +276,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ func (a *InboundController) 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) startTask() {
|
func (a *InboundController) startTask() {
|
||||||
@@ -60,20 +59,21 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,16 +93,17 @@ 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)
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
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)
|
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.addTo"), inbound, err)
|
needRestart := false
|
||||||
if err == nil {
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||||
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,12 +111,13 @@ 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)
|
needRestart := true
|
||||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
needRestart, err = a.inboundService.DelInbound(id)
|
||||||
if err == nil {
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,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,12 +133,13 @@ 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)
|
needRestart := true
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.revise"), inbound, err)
|
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
|
||||||
if err == nil {
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,37 +148,42 @@ func (a *InboundController) getClientIps(c *gin.Context) {
|
|||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
ips, err := a.inboundService.GetInboundClientIps(email)
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
||||||
if err != nil {
|
if err != nil || 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.AddInboundClient(data)
|
needRestart := true
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,18 +191,20 @@ 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")
|
||||||
|
|
||||||
err = a.inboundService.DelInboundClient(id, clientId)
|
needRestart := true
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,17 +215,19 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
needRestart := true
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,18 +235,20 @@ 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")
|
||||||
|
|
||||||
err = a.inboundService.ResetClientTraffic(id, email)
|
needRestart := true
|
||||||
|
|
||||||
|
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +258,8 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
}
|
}
|
||||||
@@ -251,7 +267,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +275,8 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
@@ -266,7 +284,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,42 +49,45 @@ 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 {
|
||||||
|
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
pureJsonMsg(c, false, I18n(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Unable to get session's max age from DB")
|
logger.Warningf("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.Warningf("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,16 +114,13 @@ 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) {
|
||||||
count := c.Param("count")
|
count := c.Param("count")
|
||||||
logs, err := a.serverService.GetLogs(count)
|
level := c.PostForm("level")
|
||||||
if err != nil {
|
syslog := c.PostForm("syslog")
|
||||||
jsonMsg(c, "getLogs", err)
|
logs := a.serverService.GetLogs(count, level, syslog)
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +139,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,39 +58,49 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
type settingFunc func() (interface{}, error)
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
settings := map[string]settingFunc{
|
||||||
return
|
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
||||||
|
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
||||||
|
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
||||||
|
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
||||||
|
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
||||||
|
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
||||||
|
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
||||||
|
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
||||||
|
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
||||||
|
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||||
|
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
|
||||||
if err != nil {
|
result := make(map[string]interface{})
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
|
||||||
return
|
for key, fn := range settings {
|
||||||
|
value, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
}
|
}
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
|
||||||
if err != nil {
|
subTLS := false
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
||||||
return
|
subTLS = true
|
||||||
}
|
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result := map[string]interface{}{
|
|
||||||
"expireDiff": expireDiff,
|
|
||||||
"trafficDiff": trafficDiff,
|
|
||||||
"defaultCert": defaultCert,
|
|
||||||
"defaultKey": defaultKey,
|
|
||||||
}
|
}
|
||||||
|
result["subTLS"] = subTLS
|
||||||
|
|
||||||
|
delete(result, "subKeyFile")
|
||||||
|
delete(result, "subCertFile")
|
||||||
|
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,27 +108,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 +137,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 +157,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Pager struct {
|
|||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
@@ -40,10 +41,20 @@ type AllSetting struct {
|
|||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
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 +65,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 +91,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
|
||||||
|
}
|
||||||
|
|||||||
80
web/global/hashStorage.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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 ? "-" + 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,42 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
||||||
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
|
return buildURL({ host, port, isTLS, base, path: subID });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
if (qrModal.client && 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,42 +60,45 @@
|
|||||||
<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="l.value" v-for="l in supportLangs">
|
||||||
<a-col :span="7">
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<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 v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</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,31 @@
|
|||||||
<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>
|
||||||
|
<br>
|
||||||
<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 +68,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 +95,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 +114,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 +144,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,32 +187,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) {
|
|
||||||
switch(protocol){
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
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(clientsBulkModal.inbound.settings.shadowsockses[0].method);
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -209,10 +229,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(clients[0].method));
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -94,68 +101,62 @@
|
|||||||
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) {
|
||||||
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) {
|
||||||
|
document.getElementById("clientIPs").value = msg.obj;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
let ips = msg.obj;
|
||||||
ips = JSON.parse(msg.obj)
|
if (typeof ips === 'string' && ips.startsWith('[') && ips.endsWith(']')) {
|
||||||
ips = ips.join(",")
|
try {
|
||||||
event.target.value = ips
|
ips = JSON.parse(ips);
|
||||||
} catch (error) {
|
ips = Array.isArray(ips) ? ips.join("\n") : ips;
|
||||||
// text
|
} catch (e) {
|
||||||
event.target.value = msg.obj
|
console.error('Error parsing JSON:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
document.getElementById("clientIPs").value = ips;
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
|
try {
|
||||||
if (!msg.success) {
|
const msg = await HttpUtil.post(`/panel/inbound/clearClientIps/${email}`);
|
||||||
return;
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("clientIPs").value = "";
|
||||||
|
} catch (error) {
|
||||||
}
|
}
|
||||||
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 +167,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,59 @@
|
|||||||
{{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.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</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="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.randomLowerAndNum(16)" 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,46 +66,51 @@
|
|||||||
<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">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</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>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</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
|
||||||
</a-textarea>
|
@click="getDBClientIps(client.email)"
|
||||||
</a-form>
|
placeholder="Click To Get IPs"
|
||||||
</a-form-item>
|
:auto-size="{ minRows: 5, maxRows: 10 }"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</a-form>
|
||||||
|
</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 +118,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 +129,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 +153,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>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "remark" }}'>
|
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label='{{ i18n "remark" }}'>
|
||||||
|
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-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 +25,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 +44,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 +53,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,15 +1,126 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
|
<template v-if="inbound.isSSMultiUser">
|
||||||
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
|
<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.randomLowerAndNum(8)" 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.randomLowerAndNum(16)" 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>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</template>
|
||||||
|
</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" @change="SSMethodChange">
|
||||||
<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>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item v-if="inbound.isSS2022" label='{{ i18n "password" }}'>
|
||||||
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
<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.randomLowerAndNum(8)" 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.randomLowerAndNum(16)" 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,71 @@
|
|||||||
<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>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<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 +144,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,137 @@
|
|||||||
{{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.randomLowerAndNum(8)" 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.randomLowerAndNum(16)" 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>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Flow</th>
|
||||||
|
<th>ID</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.flow ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<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 +152,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,50 @@
|
|||||||
{{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.randomLowerAndNum(8)" 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="ID">
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
</a-form-item>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
</a-form-item>
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
</a-form-item>
|
<span slot="label">
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
Subscription
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
</template>
|
||||||
</a-form-item>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-form-item>
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" 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 +54,63 @@
|
|||||||
<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>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>ID</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<a-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,5 +1,9 @@
|
|||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="ServiceName">
|
<a-form-item label="ServiceName">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,38 +1,47 @@
|
|||||||
{{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-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model="inbound.stream.kcp.seed" style="width: 150px;" ></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,23 +1,24 @@
|
|||||||
{{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>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></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>
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<br>
|
||||||
<a-form layout="inline">
|
|
||||||
<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 +19,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,39 @@
|
|||||||
|
|
||||||
<!-- 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 +74,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>
|
||||||
@@ -67,39 +84,51 @@
|
|||||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<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" }}'>
|
<br>
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-form-item label="Reject Unknown SNI">
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
</a-form-item>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
</a-radio-group>
|
||||||
</a-form-item>
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</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 +137,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 +173,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 +204,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,37 @@
|
|||||||
<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-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template v-if="client._totalGB > 0">
|
<template slot="content" v-if="client.email">
|
||||||
<a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]]GB</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
<a-tag v-else color="cyan">[[client._totalGB]]GB</a-tag>
|
<tr>
|
||||||
</template>
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="client.totalGB > 0">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="statsColor(record, client.email)">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
||||||
|
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
||||||
|
<template v-else>
|
||||||
|
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
||||||
|
</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</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,101 +3,121 @@
|
|||||||
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><td>
|
||||||
<table>
|
<table>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</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.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</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.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<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.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>
|
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</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-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>
|
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isQuic">
|
<template v-if="inbound.isQuic">
|
||||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
<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>
|
<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>
|
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isKcp">
|
<template v-if="inbound.isKcp">
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
<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>
|
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isGrpc">
|
<template v-if="inbound.isGrpc">
|
||||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
<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>
|
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
<td v-if="inbound.tls">
|
<td v-if="inbound.tls">
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <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>
|
tls {{ 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.xtls">
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
xtls: <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>
|
xtls {{ 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-if="inbound.reality">
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<table style="margin-bottom: 10px;">
|
<table style="margin-bottom: 10px;">
|
||||||
<tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
|
|
||||||
<td>[[ col ]]</td>
|
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "status" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
|
||||||
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "usage" }}</th>
|
<td>{{ i18n "pages.inbounds.email" }}</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.id">
|
||||||
|
<td>ID</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.id ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
||||||
|
<td>Flow</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
|
<td>Password</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.password ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "status" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
|
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientStats">
|
||||||
|
<td>{{ i18n "usage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||||
|
<a-tag color="blue">↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
||||||
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
|
||||||
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">[[ sizeFormat(infoModal.clientSettings.totalGB) ]]</a-tag>
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
|
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
||||||
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</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,17 +126,46 @@
|
|||||||
</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-row>
|
||||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
</tr>
|
<a-col :span="2">
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<td>Telegram Username</td>
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
<a-icon type="snippets"></a-icon>
|
||||||
</tr>
|
</button>
|
||||||
</table>
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
|
<a-divider>Telegram Username</a-divider>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
|
<a-col :span="2">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="dbInbound.hasLink()">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
|
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :span="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-divider></a-divider>
|
<a-divider></a-divider>
|
||||||
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
||||||
@@ -130,6 +179,19 @@
|
|||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<template v-if="inbound.protocol == Protocols.SHADOWSOCKS && !inbound.isSSMultiUser">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
|
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :span="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
||||||
@@ -177,11 +239,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
|
||||||
<a-divider>URL</a-divider>
|
|
||||||
<p>[[ infoModal.link ]]</p>
|
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
const infoModal = {
|
const infoModal = {
|
||||||
@@ -194,31 +251,52 @@
|
|||||||
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 ? "-" + 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) {
|
||||||
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
|
return buildURL({ host, port, isTLS, base, path: subID });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const infoModalApp = new Vue({
|
const infoModalApp = new Vue({
|
||||||
@@ -233,42 +311,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,32 +44,21 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const protocols = {
|
|
||||||
VMESS: Protocols.VMESS,
|
|
||||||
VLESS: Protocols.VLESS,
|
|
||||||
TROJAN: Protocols.TROJAN,
|
|
||||||
SHADOWSOCKS: Protocols.SHADOWSOCKS,
|
|
||||||
DOKODEMO: Protocols.DOKODEMO,
|
|
||||||
SOCKS: Protocols.SOCKS,
|
|
||||||
HTTP: Protocols.HTTP,
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#inbound-modal',
|
el: '#inbound-modal',
|
||||||
data: {
|
data: {
|
||||||
inModal: inModal,
|
inModal: inModal,
|
||||||
Protocols: protocols,
|
|
||||||
SSMethods: SSMethods,
|
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
get inbound() {
|
get inbound() {
|
||||||
return inModal.inbound;
|
return inModal.inbound;
|
||||||
@@ -86,9 +75,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.hostname }];
|
||||||
|
} else {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
@@ -98,12 +99,41 @@
|
|||||||
if (!inModal.inbound.canEnableReality()) {
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
this.inModal.inbound.reality = false;
|
this.inModal.inbound.reality = false;
|
||||||
}
|
}
|
||||||
|
if (this.inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) {
|
||||||
|
this.inModal.inbound.settings.vlesses.forEach(client => {
|
||||||
|
client.flow = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setDefaultCertData(){
|
SSMethodChange() {
|
||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
if (this.inModal.inbound.isSSMultiUser) {
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||||
|
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
||||||
|
}
|
||||||
|
if (["aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305"].includes(this.inModal.inbound.settings.method)) {
|
||||||
|
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||||
|
client.method = this.inModal.inbound.settings.method;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||||
|
client.method = "";
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.inModal.inbound.settings.shadowsockses.length > 0){
|
||||||
|
this.inModal.inbound.settings.shadowsockses = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getNewX25519Cert(){
|
setDefaultCertData(index) {
|
||||||
|
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
||||||
|
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
||||||
|
},
|
||||||
|
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 +142,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,16 @@
|
|||||||
<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">
|
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
|
||||||
|
<a-icon type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.toInbound().isSSMultiUser">
|
||||||
<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 +167,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 +189,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>
|
||||||
@@ -198,12 +210,29 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
|
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template v-if="dbInbound.total > 0">
|
<template slot="content">
|
||||||
<a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
<a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<tr>
|
||||||
</template>
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
||||||
|
</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
@@ -230,7 +259,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.toInbound().isSSMultiUser"
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerTrojanColumns"
|
:columns="innerTrojanColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
@@ -247,8 +276,8 @@
|
|||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -263,7 +292,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',
|
||||||
@@ -282,12 +311,12 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 50,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 120,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
@@ -298,20 +327,20 @@
|
|||||||
|
|
||||||
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: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UID', width: 120, dataIndex: "id" },
|
{ title: 'UUID', width: 120, dataIndex: "id" },
|
||||||
];
|
];
|
||||||
|
|
||||||
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: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'Password', width: 120, dataIndex: "password" },
|
{ title: 'Password', width: 170, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -319,71 +348,106 @@
|
|||||||
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,
|
||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
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);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
|
this.clientCount.splice(0);
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
const dbInbound = new DBInbound(inbound);
|
const dbInbound = new DBInbound(inbound);
|
||||||
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);
|
if (inbound.protocol === Protocols.SHADOWSOCKS && (!to_inbound.isSSMultiUser)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 +473,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 +487,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 +572,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 +587,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 +595,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 +622,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 +651,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 +670,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 +716,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 +754,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 +826,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 +837,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 +940,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 +992,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
@@ -868,5 +1002,6 @@
|
|||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -13,32 +13,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.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: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
|
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
: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 +53,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 +62,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,26 +77,22 @@
|
|||||||
<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: <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>
|
<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 "menu.link" }}:
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tooltip>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<template slot="title">
|
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
{{ i18n "pages.index.operationHoursDesc" }}
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</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,35 +107,78 @@
|
|||||||
</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" }}:
|
<a-row>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
|
<a-col :span="12">
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</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] ]]
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
IPv4:
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
[[ status.publicIP.ipv4 ]]
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
IPv6:
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
[[ status.publicIP.ipv6 ]]
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
TCP: [[ status.tcpCount ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
UDP: [[ status.udpCount ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</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 ]]
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.connectionCountDesc" }}
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
|
||||||
<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>
|
||||||
[[ sizeFormat(status.netIO.up) ]] / S
|
[[ sizeFormat(status.netIO.up) ]]/S
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
@@ -147,7 +188,7 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-icon type="arrow-down"></a-icon>
|
||||||
[[ sizeFormat(status.netIO.down) ]] / S
|
[[ sizeFormat(status.netIO.down) ]]/S
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
@@ -159,7 +200,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 +229,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,25 +243,41 @@
|
|||||||
</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">
|
||||||
<a-form-item label="Count">
|
<a-form-item label="Count">
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs(logModal.rows)"
|
@change="openLogs()"
|
||||||
: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>
|
||||||
<a-select-option value="100">100</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Log Level">
|
||||||
|
<a-select v-model="logModal.level"
|
||||||
|
style="width: 120px"
|
||||||
|
@change="openLogs()"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
|
<a-select-option value="err">Error</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="SysLog">
|
||||||
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
|
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
@@ -227,12 +285,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>
|
||||||
|
|
||||||
@@ -272,26 +349,32 @@
|
|||||||
class Status {
|
class Status {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.cpu = new CurTotal(0, 0);
|
this.cpu = new CurTotal(0, 0);
|
||||||
|
this.cpuCores = 0;
|
||||||
|
this.cpuSpeedMhz = 0;
|
||||||
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.publicIP = { ipv4: 0, ipv6: 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;
|
||||||
}
|
}
|
||||||
this.cpu = new CurTotal(data.cpu, 100);
|
this.cpu = new CurTotal(data.cpu, 100);
|
||||||
|
this.cpuCores = data.cpuCores;
|
||||||
|
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
||||||
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
||||||
this.loads = data.loads.map(load => toFixed(load, 2));
|
this.loads = data.loads.map(load => toFixed(load, 2));
|
||||||
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
||||||
this.netIO = data.netIO;
|
this.netIO = data.netIO;
|
||||||
this.netTraffic = data.netTraffic;
|
this.netTraffic = data.netTraffic;
|
||||||
|
this.publicIP = data.publicIP;
|
||||||
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
||||||
this.tcpCount = data.tcpCount;
|
this.tcpCount = data.tcpCount;
|
||||||
this.udpCount = data.udpCount;
|
this.udpCount = data.udpCount;
|
||||||
@@ -329,10 +412,34 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
logs: '',
|
logs: '',
|
||||||
rows: 20,
|
rows: 20,
|
||||||
show(logs, rows) {
|
level: 'info',
|
||||||
|
syslog: false,
|
||||||
|
show(logs) {
|
||||||
|
this.visible = true;
|
||||||
|
this.logs = logs? logs.join("\n"): "No Record...";
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
this.visible = true;
|
||||||
this.rows = rows;
|
|
||||||
this.logs = logs.join("\n");
|
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -344,9 +451,11 @@
|
|||||||
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"}}',
|
||||||
},
|
},
|
||||||
@@ -356,9 +465,13 @@
|
|||||||
this.loadingTip = tip;
|
this.loadingTip = tip;
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
try {
|
||||||
if (msg.success) {
|
const msg = await HttpUtil.post('/server/status');
|
||||||
this.setStatus(msg.obj);
|
if (msg.success) {
|
||||||
|
this.setStatus(msg.obj);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to get status:", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setStatus(data) {
|
setStatus(data) {
|
||||||
@@ -378,17 +491,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 +509,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,34 +517,77 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(rows){
|
async openLogs(){
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/logs/'+rows);
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj,rows);
|
logModal.show(msg.obj);
|
||||||
},
|
},
|
||||||
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) {
|
let retries = 0;
|
||||||
|
while (retries < 5) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
|
retries = 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error("Error occurred while fetching status:", e);
|
||||||
|
retries++;
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
1118
web/html/xui/settings.html
Normal file
@@ -2,28 +2,28 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/xray"
|
|
||||||
|
|
||||||
"net"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-cmd/cmd"
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
type CheckClientIpJob struct {}
|
||||||
xrayService service.XrayService
|
|
||||||
}
|
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var disAllowedIps []string
|
var disAllowedIps []string
|
||||||
|
var ipFiles = []string{
|
||||||
|
xray.GetIPLimitLogPath(),
|
||||||
|
xray.GetIPLimitBannedLogPath(),
|
||||||
|
xray.GetAccessPersistentLogPath(),
|
||||||
|
}
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
job = new(CheckClientIpJob)
|
job = new(CheckClientIpJob)
|
||||||
@@ -32,30 +32,59 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
|||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
logger.Debug("Check Client IP Job...")
|
logger.Debug("Check Client IP Job...")
|
||||||
processLogFile()
|
|
||||||
|
|
||||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
// create files required for iplimit if not exists
|
||||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
for i := 0; i < len(ipFiles); i++ {
|
||||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
defer file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for limit ip
|
||||||
|
if j.hasLimitIp() {
|
||||||
|
j.processLogFile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLogFile() {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
accessLogPath := GetAccessLogPath()
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
|
||||||
|
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
if inbound.Settings == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := map[string][]model.Client{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
clients := settings["clients"]
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
limitIp := client.LimitIP
|
||||||
|
if limitIp > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) processLogFile() {
|
||||||
|
accessLogPath := xray.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
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
data, err := os.ReadFile(accessLogPath)
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
// clean log
|
|
||||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
|
||||||
checkError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@@ -73,10 +102,10 @@ 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 j.contains(InboundClientIps[matchesEmail], ip) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
@@ -85,54 +114,50 @@ func processLogFile() {
|
|||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disAllowedIps = []string{}
|
disAllowedIps = []string{}
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
inboundClientIps, err := j.getInboundClientIps(clientEmail)
|
||||||
sort.Strings(ips)
|
sort.Strings(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addInboundClientIps(clientEmail, ips)
|
j.addInboundClientIps(clientEmail, ips)
|
||||||
} else {
|
} else {
|
||||||
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
LimitDevice := func() { LimitDevice() }
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
stop := schedule(LimitDevice, 1000*time.Millisecond)
|
if shouldCleanLog {
|
||||||
time.Sleep(10 * time.Second)
|
// copy access log to persistent file
|
||||||
stop <- true
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
j.checkError(err)
|
||||||
|
input, err := os.ReadFile(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
if _, err := logAccessP.Write(input); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
|
}
|
||||||
|
defer logAccessP.Close()
|
||||||
|
|
||||||
}
|
// clean access log
|
||||||
func GetAccessLogPath() string {
|
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
config, err := os.ReadFile(xray.GetConfigPath())
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
jsonConfig := map[string]interface{}{}
|
|
||||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
|
||||||
checkError(err)
|
|
||||||
if jsonConfig["log"] != nil {
|
|
||||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
|
||||||
if jsonLog["access"] != nil {
|
|
||||||
|
|
||||||
accessLogPath := jsonLog["access"].(string)
|
|
||||||
|
|
||||||
return accessLogPath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
|
||||||
}
|
}
|
||||||
func checkError(e error) {
|
|
||||||
|
func (j *CheckClientIpJob) checkError(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
logger.Warning("client ip job err:", e)
|
logger.Warning("client ip job err:", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func contains(s []string, str string) bool {
|
|
||||||
|
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
if v == str {
|
if v == str {
|
||||||
return true
|
return true
|
||||||
@@ -141,7 +166,8 @@ func contains(s []string, str string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|
||||||
|
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
InboundClientIps := &model.InboundClientIps{}
|
||||||
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
||||||
@@ -150,13 +176,11 @@ func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|||||||
}
|
}
|
||||||
return InboundClientIps, nil
|
return InboundClientIps, nil
|
||||||
}
|
}
|
||||||
func addInboundClientIps(clientEmail string, ips []string) error {
|
|
||||||
|
func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error {
|
||||||
inboundClientIps := &model.InboundClientIps{}
|
inboundClientIps := &model.InboundClientIps{}
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
j.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)
|
||||||
@@ -178,37 +202,50 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) error {
|
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
|
|
||||||
// check inbound limitation
|
// check inbound limitation
|
||||||
inbound, err := GetInboundByEmail(clientEmail)
|
inbound, err := j.getInboundByEmail(clientEmail)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
if inbound.Settings == "" {
|
if inbound.Settings == "" {
|
||||||
logger.Debug("wrong data ", inbound)
|
logger.Debug("wrong data ", inbound)
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := map[string][]model.Client{}
|
settings := map[string][]model.Client{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
var disAllowedIps []string // initialize the slice
|
// create iplimit log file channel
|
||||||
|
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||||
|
}
|
||||||
|
defer logIpFile.Close()
|
||||||
|
log.SetOutput(logIpFile)
|
||||||
|
log.SetFlags(log.LstdFlags)
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email == clientEmail {
|
if client.Email == clientEmail {
|
||||||
|
|
||||||
limitIp := client.LimitIP
|
limitIp := client.LimitIP
|
||||||
|
|
||||||
if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
|
if limitIp != 0 {
|
||||||
|
shouldCleanLog = true
|
||||||
|
|
||||||
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
if limitIp < len(ips) && inbound.Enable {
|
||||||
|
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
||||||
|
for i := limitIp; i < len(ips); i++ {
|
||||||
|
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,132 +255,19 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
|||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
return nil
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableInbound(id int) error {
|
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
|
||||||
result := db.Model(model.Inbound{}).
|
|
||||||
Where("id = ? and enable = ?", id, true).
|
|
||||||
Update("enable", false)
|
|
||||||
err := result.Error
|
|
||||||
logger.Warning("disable inbound with id:", id)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
job.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds *model.Inbound
|
var inbounds *model.Inbound
|
||||||
|
|
||||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice() {
|
|
||||||
var destIp, destPort, srcIp, srcPort string
|
|
||||||
|
|
||||||
localIp, err := LocalIP()
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
|
|
||||||
|
|
||||||
<-c.Start()
|
|
||||||
if len(c.Status().Stdout) > 0 {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
|
||||||
|
|
||||||
data := strings.Split(row, " ")
|
|
||||||
|
|
||||||
if len(data) < 2 {
|
|
||||||
continue // Skip this row if it doesn't have at least two elements
|
|
||||||
}
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
|
||||||
destPort = portRegx.FindString(data[0])
|
|
||||||
destPort = strings.Replace(destPort, ":", "", -1)
|
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
|
||||||
srcPort = strings.Replace(srcPort, ":", "", -1)
|
|
||||||
|
|
||||||
if contains(disAllowedIps, srcIp) {
|
|
||||||
dropCmd := cmd.NewCmd("bash", "-c", "ss -K dport = "+srcPort)
|
|
||||||
dropCmd.Start()
|
|
||||||
|
|
||||||
logger.Debug("request droped : ", srcIp, srcPort, "to", destIp, destPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
|
||||||
// get machine ips
|
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
ips := []string{}
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, i := range ifaces {
|
|
||||||
addrs, err := i.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
ips = append(ips, ip.String())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("System IPs : ", ips)
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IPsToRegex(ips []string) string {
|
|
||||||
|
|
||||||
regx := ""
|
|
||||||
for _, ip := range ips {
|
|
||||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
|
||||||
|
|
||||||
}
|
|
||||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
|
||||||
|
|
||||||
return regx
|
|
||||||
}
|
|
||||||
|
|
||||||
func schedule(LimitDevice func(), delay time.Duration) chan bool {
|
|
||||||
stop := make(chan bool)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
LimitDevice()
|
|
||||||
select {
|
|
||||||
case <-time.After(delay):
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -15,19 +15,23 @@ func NewCheckInboundJob() *CheckInboundJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckInboundJob) Run() {
|
func (j *CheckInboundJob) Run() {
|
||||||
count, err := j.inboundService.DisableInvalidClients()
|
needRestart, count, err := j.inboundService.DisableInvalidClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("disable invalid Client err:", err)
|
logger.Warning("Error in disabling invalid clients:", err)
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("disabled %v Client", count)
|
logger.Debugf("%v clients disabled", count)
|
||||||
j.xrayService.SetToNeedRestart()
|
if needRestart {
|
||||||
|
j.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err = j.inboundService.DisableInvalidInbounds()
|
needRestart, count, err = j.inboundService.DisableInvalidInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("disable invalid inbounds err:", err)
|
logger.Warning("Error in disabling invalid inbounds:", err)
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("disabled %v inbounds", count)
|
logger.Debugf("%v inbounds disabled", count)
|
||||||
j.xrayService.SetToNeedRestart()
|
if needRestart {
|
||||||
|
j.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||