mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-20 17:45:49 +00:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
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.2
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.1.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v2.1.0
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4.4.0
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v4.0.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
tmp
|
tmp
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
|
|||||||
54
Dockerfile
Normal file
54
Dockerfile
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Use the official Golang image as the base image
|
||||||
|
FROM golang:1.20 as builder
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETOS
|
||||||
|
|
||||||
|
# Set up the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the Go modules and download the dependencies
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the X-ui binary
|
||||||
|
RUN CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o xui-release-${TARGETARCH} -v main.go
|
||||||
|
|
||||||
|
# Start a new stage using the base image
|
||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# Set up the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the X-ui binary and required files from the builder stage
|
||||||
|
COPY --from=builder /app/xui-release-${TARGETARCH} /app/x-ui/xui-release
|
||||||
|
COPY x-ui.service /app/x-ui/x-ui.service
|
||||||
|
COPY x-ui.sh /app/x-ui/x-ui.sh
|
||||||
|
|
||||||
|
# Set up the runtime environment
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app/x-ui/bin
|
||||||
|
|
||||||
|
# Download and set up the required files
|
||||||
|
RUN 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-\${TARGETARCH}
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN chmod +x /app/x-ui/x-ui.sh
|
||||||
|
|
||||||
|
# Set the entrypoint
|
||||||
|
ENTRYPOINT ["/app/x-ui/x-ui.sh"]
|
||||||
28
README.md
28
README.md
@@ -1,4 +1,5 @@
|
|||||||
# 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)
|
||||||
[](#)
|
[](#)
|
||||||
@@ -6,11 +7,12 @@
|
|||||||
[](#)
|
[](#)
|
||||||
[](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)**
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
**Buy Me a Coffee :**
|
||||||
|
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
|
|
||||||
# Install & Upgrade
|
# Install & Upgrade
|
||||||
|
|
||||||
@@ -20,10 +22,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## Install custom version
|
## Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.3.3`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.4.0`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.3.3
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.4.0
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
@@ -33,8 +35,8 @@ apt-get install certbot -y
|
|||||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
certbot renew --dry-run
|
certbot renew --dry-run
|
||||||
```
|
```
|
||||||
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
|
||||||
|
|
||||||
|
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
|
|
||||||
@@ -116,6 +118,7 @@ If you want to use routing to WARP follow steps as below:
|
|||||||
- For more advanced configuration items, please refer to the panel
|
- For more advanced configuration items, please refer to the panel
|
||||||
- Fix api routes (user setting will create with api)
|
- Fix api routes (user setting will create with api)
|
||||||
- Support to change configs by different items provided in panel
|
- Support to change configs by different items provided in panel
|
||||||
|
- Support export/import database from panel
|
||||||
|
|
||||||
# Tg robot use
|
# Tg robot use
|
||||||
|
|
||||||
@@ -170,13 +173,19 @@ Reference syntax:
|
|||||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||||
| `POST` | `"/addClient"` | Add Client to inbound |
|
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by UID/Password as clientId |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update Client by UID/Password as clientId |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
|
*- The field `clientId` should be filled by:
|
||||||
|
- `client.id` for VMESS and VLESS
|
||||||
|
- `client.password` for TROJAN
|
||||||
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
@@ -190,11 +199,6 @@ Reference syntax:
|
|||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
|
||||||
# Buy Me a Coffee
|
|
||||||
|
|
||||||
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
|
|
||||||
|
|
||||||
# Pictures
|
# Pictures
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.3.4
|
1.4.0
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -104,3 +106,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.Reader) (bool, error) {
|
||||||
|
signature := []byte("SQLite format 3\x00")
|
||||||
|
buf := make([]byte, len(signature))
|
||||||
|
_, err := file.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return bytes.Equal(buf, signature), nil
|
||||||
|
}
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -13,11 +13,11 @@ require (
|
|||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3
|
github.com/shirou/gopsutil/v3 v3.23.4
|
||||||
github.com/xtls/xray-core v1.8.1
|
github.com/xtls/xray-core v1.8.1
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.9.0
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.55.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.0
|
||||||
gorm.io/gorm v1.25.0
|
gorm.io/gorm v1.25.0
|
||||||
)
|
)
|
||||||
@@ -30,7 +30,7 @@ require (
|
|||||||
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.13.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
@@ -39,7 +39,7 @@ require (
|
|||||||
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/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/leodido/go-urn v1.2.3 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||||
|
|||||||
21
go.sum
21
go.sum
@@ -42,8 +42,8 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
|||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ=
|
||||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
@@ -85,8 +85,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||||
@@ -134,9 +134,8 @@ github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
|||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||||
@@ -173,8 +172,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
||||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
@@ -235,8 +234,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -212,8 +212,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!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
32
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
32
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
@@ -992,11 +992,11 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}
|
.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}
|
||||||
.ant-menu-item>.ant-badge>a:hover{color:#1890ff}
|
.ant-menu-item>.ant-badge>a:hover{color:#1890ff}
|
||||||
.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}
|
.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}
|
||||||
.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#fff;background-image:linear-gradient(-20deg,#1a61b3 0,#242d81 100%)}
|
.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#1890ff;background-color:#ebebeb}
|
||||||
.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:#e6f7ff}
|
||||||
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
||||||
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
||||||
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
||||||
@@ -1032,7 +1032,7 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-horizontal>.ant-menu-item-selected>a{color:#1890ff}
|
.ant-menu-horizontal>.ant-menu-item-selected>a{color:#1890ff}
|
||||||
.ant-menu-horizontal:after{display:block;clear:both;height:0;content:"\20"}
|
.ant-menu-horizontal:after{display:block;clear:both;height:0;content:"\20"}
|
||||||
.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item{position:relative}
|
.ant-menu-inline .ant-menu-item,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item{position:relative}
|
||||||
.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;/*! border-right:3px solid #1890ff; */transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}
|
.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #1890ff;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}
|
||||||
.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}
|
.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}
|
||||||
.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu{padding-bottom:.02px}
|
.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu{padding-bottom:.02px}
|
||||||
.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child){margin-bottom:8px}
|
.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child){margin-bottom:8px}
|
||||||
@@ -1080,8 +1080,9 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-dark .ant-menu-item:hover{background-color:transparent}
|
.ant-menu-dark .ant-menu-item:hover{background-color:transparent}
|
||||||
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#fff}
|
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#206bd3}
|
||||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#1890ff}
|
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#15223a}
|
||||||
|
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#383838}
|
||||||
.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}
|
||||||
@@ -1263,10 +1264,10 @@ 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:1px solid #e8e8e8;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}
|
||||||
@@ -1356,11 +1357,12 @@ to{transform:scale(1.6);opacity:0}
|
|||||||
.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}
|
.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}
|
||||||
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
||||||
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#1890ff}
|
.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#1890ff}
|
||||||
.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1}
|
.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1;border-radius:10px;box-shadow:0 1px 7px -1px #0000005c;transition:all .3s}
|
||||||
|
.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:#1890ff;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:""}
|
||||||
@@ -2977,7 +2979,7 @@ 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 #e8e8e8;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%}
|
||||||
@@ -3259,7 +3261,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-input-number-handler-down:hover{height:60%!important}
|
.ant-input-number-handler-down:hover{height:60%!important}
|
||||||
.ant-input-number-handler-down-disabled,.ant-input-number-handler-up-disabled{cursor:not-allowed}
|
.ant-input-number-handler-down-disabled,.ant-input-number-handler-up-disabled{cursor:not-allowed}
|
||||||
.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner,.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner{color:rgba(0,0,0,.25)}
|
.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner,.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner{color:rgba(0,0,0,.25)}
|
||||||
.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#f0f2f5}
|
.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#fff}
|
||||||
.ant-layout,.ant-layout *{box-sizing:border-box}
|
.ant-layout,.ant-layout *{box-sizing:border-box}
|
||||||
.ant-layout.ant-layout-has-sider{flex-direction:row}
|
.ant-layout.ant-layout-has-sider{flex-direction:row}
|
||||||
.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{overflow-x:hidden}
|
.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{overflow-x:hidden}
|
||||||
@@ -3276,9 +3278,9 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#001529;border-radius:0 4px 4px 0;cursor:pointer;transition:background .3s ease}
|
.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#001529;border-radius:0 4px 4px 0;cursor:pointer;transition:background .3s ease}
|
||||||
.ant-layout-sider-zero-width-trigger:hover{background:#192c3e}
|
.ant-layout-sider-zero-width-trigger:hover{background:#192c3e}
|
||||||
.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:#f7f7f7}
|
||||||
.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}
|
.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}
|
||||||
.ant-list{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}
|
.ant-list{box-sizing:border-box;margin:1.5em;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}
|
||||||
.ant-list *{outline:0}
|
.ant-list *{outline:0}
|
||||||
.ant-list-pagination{margin-top:24px;text-align:right}
|
.ant-list-pagination{margin-top:24px;text-align:right}
|
||||||
.ant-list-pagination .ant-pagination-options{text-align:left}
|
.ant-list-pagination .ant-pagination-options{text-align:left}
|
||||||
@@ -3303,7 +3305,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-list-footer,.ant-list-header{background:0 0}
|
.ant-list-footer,.ant-list-header{background:0 0}
|
||||||
.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}
|
.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}
|
||||||
.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}
|
.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}
|
||||||
.ant-list-split .ant-list-item{border-bottom:1px solid #e8e8e8}
|
.ant-list-split .ant-list-item{border-bottom:1px solid rgb(153 153 153 / 20%)}
|
||||||
.ant-list-split .ant-list-item:last-child{border-bottom:none}
|
.ant-list-split .ant-list-item:last-child{border-bottom:none}
|
||||||
.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}
|
.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}
|
||||||
.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}
|
.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}
|
||||||
@@ -3676,7 +3678,7 @@ to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}
|
|||||||
.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:#090b0e29}
|
||||||
.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:#1890ff}
|
||||||
.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}
|
||||||
|
|||||||
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-space {
|
.ant-space {
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card {
|
.ant-card {
|
||||||
border-radius: 30px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-hoverable {
|
.ant-card-hoverable {
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
.ant-layout-sider-zero-width-trigger,
|
.ant-layout-sider-zero-width-trigger,
|
||||||
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu,
|
||||||
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu {
|
||||||
background:#161b22
|
background:#1A202B
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark {
|
.ant-card-dark {
|
||||||
@@ -180,7 +180,11 @@
|
|||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-card-dark:hover {
|
||||||
border-color: #e8e8e8;
|
border-color: #e8e8e8;
|
||||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
/* box-shadow: 0 3px 12px -0.8px #0000005c; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
@@ -236,6 +240,10 @@
|
|||||||
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 +252,8 @@
|
|||||||
.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-card-dark .ant-select-disabled .ant-select-selection {
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
@@ -397,3 +406,17 @@
|
|||||||
.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: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #11141a57;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #11141a66;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
@@ -3,9 +3,13 @@ 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)
|
||||||
|
|||||||
@@ -17,15 +17,8 @@ const VmessMethods = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
|
||||||
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
|
||||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
|
||||||
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
const XTLS_FLOW_CONTROL = {
|
||||||
@@ -939,7 +932,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
this._protocol = protocol;
|
this._protocol = protocol;
|
||||||
this.settings = Inbound.Settings.getSettings(protocol);
|
this.settings = Inbound.Settings.getSettings(protocol);
|
||||||
if (protocol === Protocols.TROJAN) {
|
if (protocol === Protocols.TROJAN) {
|
||||||
this.tls = true;
|
this.tls = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,66 +1005,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.network === "http";
|
return this.network === "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMess & VLess
|
|
||||||
get uuid() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].id;
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].id;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VLess & Trojan
|
|
||||||
get flow() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].flow;
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].flow;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VMess
|
|
||||||
get alterId() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].alterId;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Socks & HTTP
|
|
||||||
get username() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].user;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trojan & Shadowsocks & Socks & HTTP
|
|
||||||
get password() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].password;
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return this.settings.password;
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].pass;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
get method() {
|
get method() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -1146,9 +1079,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if(this.settings.trojans[index].expiryTime > 0)
|
if(this.settings.trojans[index].expiryTime > 0)
|
||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
if(this.settings.shadowsockses[index].expiryTime > 0)
|
||||||
|
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1096,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1228,7 +1164,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1413,6 +1348,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.xtls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
|
if (this.stream.xtls.settings.serverName !== ''){
|
||||||
|
params.set("sni", this.stream.xtls.settings.serverName);
|
||||||
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1443,13 +1381,11 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', remark='') {
|
genSSLink(address='', remark='', clientIndex = 0) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const server = this.stream.tls.server;
|
const port = this.port;
|
||||||
if (!ObjectUtil.isEmpty(server)) {
|
|
||||||
address = server;
|
return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark);
|
||||||
}
|
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1545,6 +1481,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.xtls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
|
if (this.stream.xtls.settings.serverName !== ''){
|
||||||
|
params.set("sni", this.stream.xtls.settings.serverName);
|
||||||
|
}
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1569,7 +1508,11 @@ class Inbound extends XrayCommonClass {
|
|||||||
remark += '-' + 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:
|
||||||
|
if (this.settings.shadowsockses[clientIndex].email != ""){
|
||||||
|
remark = this.settings.shadowsockses[clientIndex].email
|
||||||
|
}
|
||||||
|
return this.genSSLink(address, remark, clientIndex);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
if (this.settings.trojans[clientIndex].email != ""){
|
||||||
remark += '-' + this.settings.trojans[clientIndex].email
|
remark += '-' + this.settings.trojans[clientIndex].email
|
||||||
@@ -2033,13 +1976,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 +1993,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 +2002,77 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
method: this.method,
|
method: this.method,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
|
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
|
super();
|
||||||
|
this.password = password;
|
||||||
|
this.email = email;
|
||||||
|
this.limitIp = limitIp;
|
||||||
|
this.totalGB = totalGB;
|
||||||
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
password: this.password,
|
||||||
|
email: this.email,
|
||||||
|
limitIp: this.limitIp,
|
||||||
|
totalGB: this.totalGB,
|
||||||
|
expiryTime: this.expiryTime,
|
||||||
|
enable: this.enable,
|
||||||
|
tgId: this.tgId,
|
||||||
|
subId: this.subId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||||
|
json.password,
|
||||||
|
json.email,
|
||||||
|
json.limitIp,
|
||||||
|
json.totalGB,
|
||||||
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get _expiryTime() {
|
||||||
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
|
return moment(this.expiryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _expiryTime(t) {
|
||||||
|
if (t == null || t === "") {
|
||||||
|
this.expiryTime = 0;
|
||||||
|
} else {
|
||||||
|
this.expiryTime = t.valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get _totalGB() {
|
||||||
|
return toFixed(this.totalGB / ONE_GB, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _totalGB(gb) {
|
||||||
|
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -165,6 +165,12 @@ class RandomUtil {
|
|||||||
str += this.randomShortIdSeq(8)
|
str += this.randomShortIdSeq(8)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randomShadowsocksPassword(){
|
||||||
|
let array = new Uint8Array(32);
|
||||||
|
window.crypto.getRandomValues(array);
|
||||||
|
return btoa(String.fromCharCode.apply(null, array));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.addTo"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.create"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
@@ -101,7 +101,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound.Enable = true
|
inbound.Enable = true
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
inbound, err = a.inboundService.AddInbound(inbound)
|
inbound, err = a.inboundService.AddInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.addTo"), inbound, err)
|
jsonMsgObj(c, I18n(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
@@ -131,11 +131,11 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = c.ShouldBind(inbound)
|
err = c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
inbound, err = a.inboundService.UpdateInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.revise"), inbound, err)
|
jsonMsgObj(c, I18n(c, "pages.inbounds.update"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
|||||||
|
|
||||||
err := a.inboundService.ClearClientIps(email)
|
err := a.inboundService.ClearClientIps(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Revise", err)
|
jsonMsg(c, "Update", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
@@ -165,7 +165,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
data := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(data)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
@@ -205,7 +205,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
@@ -251,7 +251,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, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,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, I18n(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelDepletedClients(id)
|
err = a.inboundService.DelDepletedClients(id)
|
||||||
|
|||||||
@@ -41,6 +41,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +100,8 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray stoped", err)
|
jsonMsg(c, "Xray stoped", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) restartXrayService(c *gin.Context) {
|
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||||
err := a.serverService.RestartXrayService()
|
err := a.serverService.RestartXrayService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,7 +109,6 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray restarted", err)
|
jsonMsg(c, "Xray restarted", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) getLogs(c *gin.Context) {
|
func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
@@ -144,6 +144,28 @@ func (a *ServerController) getDb(c *gin.Context) {
|
|||||||
c.Writer.Write(db)
|
c.Writer.Write(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
@@ -58,7 +58,7 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
@@ -67,22 +67,22 @@ func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
|||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
expireDiff, err := a.settingService.GetExpireDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
trafficDiff, err := a.settingService.GetTrafficDiff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
defaultCert, err := a.settingService.GetCertFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
defaultKey, err := a.settingService.GetKeyFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
@@ -98,27 +98,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, I18n(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, I18n(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, I18n(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, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(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, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(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 +127,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, I18n(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, I18n(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, I18n(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,7 +147,7 @@ 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, I18n(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)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
: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"
|
||||||
|
|||||||
@@ -33,6 +33,30 @@
|
|||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<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>
|
||||||
|
<span slot="label">
|
||||||
|
Telegram ID
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
@@ -43,8 +67,9 @@
|
|||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
@@ -57,15 +82,9 @@
|
|||||||
<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,11 +94,13 @@
|
|||||||
</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">
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -56,14 +56,23 @@
|
|||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientId(protocol, client) {
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.TROJAN: return client.password;
|
||||||
|
case Protocols.SHADOWSOCKS: return client.email;
|
||||||
|
default: return client.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
addClient(protocol, clients) {
|
addClient(protocol, clients) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
|
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<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 }}xui/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 }}xui/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
|
|||||||
@@ -9,7 +9,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>
|
||||||
<template v-else-if="type === 'textarea'">
|
<template v-else-if="type === 'textarea'">
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<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 label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
@@ -13,24 +17,41 @@
|
|||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<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-input v-model.trim="client.email" style="width: 200px;" ></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.inbounds.enable" }}">
|
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="client.password" style="width: 300px;" ></a-input>
|
||||||
</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 label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
||||||
|
<a-input-number v-model="client.alterId"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
<a-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">
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<span slot="label">
|
||||||
</a-form-item>
|
Subscription
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<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="client.subId"></a-input>
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<a-form-item v-if="client.email">
|
||||||
|
<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-input v-model.trim="client.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -43,7 +64,7 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
@@ -68,13 +89,14 @@
|
|||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<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>
|
||||||
@@ -82,7 +104,7 @@
|
|||||||
</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 +112,9 @@
|
|||||||
<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) ]]
|
||||||
@@ -104,11 +126,13 @@
|
|||||||
</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">
|
||||||
|
|||||||
@@ -24,12 +24,14 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="inbound.listen"></a-input>
|
<a-input v-model.trim="inbound.listen"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input type="number" v-model.number="inbound.port"></a-input>
|
<a-input-number v-model="inbound.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
<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,4 +1,109 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon @click="getNewEmail(client)" type="sync"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 150px;"></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">
|
||||||
|
<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="client.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email">
|
||||||
|
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-collapse v-else>
|
||||||
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
||||||
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
|||||||
@@ -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,6 +13,7 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{{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>
|
||||||
@@ -10,22 +9,39 @@
|
|||||||
<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-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-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<a-form-item label="Password">
|
||||||
<a-form-item label="Password">
|
<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 v-if="client.email">
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<span slot="label">
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
Subscription
|
||||||
</a-form-item>
|
<a-tooltip>
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
<template slot="title">
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
</a-form-item>
|
</template>
|
||||||
<a-form-item>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email">
|
||||||
|
<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,54 +51,64 @@
|
|||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
</tr>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
</a-form-item>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-collapse-panel>
|
||||||
</tr>
|
</a-collapse>
|
||||||
</table>
|
<a-collapse v-else>
|
||||||
</a-collapse-panel>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
</a-collapse>
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<template v-if="inbound.isTcp">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
@@ -115,7 +141,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,8 +1,7 @@
|
|||||||
{{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>
|
||||||
@@ -13,82 +12,109 @@
|
|||||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
<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-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<a-form-item label="ID">
|
||||||
<a-form-item label="ID">
|
<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 v-if="client.email">
|
||||||
|
<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="client.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-form-item v-if="client.email">
|
||||||
<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>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</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-input v-model.trim="client.tgId"></a-input>
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<span slot="label">
|
||||||
</a-tooltip>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
</span>
|
<a-tooltip>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
</template>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
</a-tooltip>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
</span>
|
||||||
</a-select>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
<br>
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option value="" selected>{{ 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 v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-tooltip>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<template slot="title">
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
</a-select>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-form-item>
|
||||||
</a-tooltip>
|
<span slot="label">
|
||||||
</span>
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
<a-form-item>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
<span slot="label">
|
</template>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-tooltip>
|
</a-tooltip>
|
||||||
<template slot="title">
|
</span>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
</template>
|
</a-form-item>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<br>
|
||||||
</a-tooltip>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
</span>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
</a-form-item>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<br>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
</a-form-item>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-collapse-panel>
|
</a-form-item>
|
||||||
</a-collapse>
|
<a-form-item v-else>
|
||||||
<a-collapse v-else>
|
<span slot="label">
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
<table width="100%">
|
<a-tooltip>
|
||||||
<tr class="client-table-header">
|
<template slot="title">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</tr>
|
</template>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-tooltip>
|
||||||
</tr>
|
</span>
|
||||||
</table>
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
</a-collapse-panel>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
</a-collapse>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-collapse v-else>
|
||||||
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<template v-if="inbound.isTcp">
|
<template v-if="inbound.isTcp">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
@@ -121,7 +147,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,8 +1,7 @@
|
|||||||
{{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>
|
||||||
@@ -15,20 +14,39 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<br>
|
||||||
<a-form-item label="ID">
|
<a-form-item label='{{ i18n "additional" }} ID'>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input-number v-model="client.alterId"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label="ID">
|
||||||
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.email">
|
||||||
|
<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="client.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
<a-form-item v-if="client.email">
|
||||||
<a-input type="number" v-model.number="client.alterId" style="width: 70px;"></a-input>
|
<span slot="label">
|
||||||
</a-form-item>
|
Telegram ID
|
||||||
<a-form-item label="Subscription" v-if="client.email">
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.subId"></a-input>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
<a-form-item label="Telegram Username" v-if="client.email">
|
</template>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-form-item>
|
</a-tooltip>
|
||||||
<a-form-item>
|
</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 +56,61 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;"></a-input>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<br>
|
||||||
<span slot="label">
|
<a-form-item>
|
||||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
<span slot="label">
|
||||||
<a-tooltip>
|
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||||
<template slot="title">
|
<a-tooltip>
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
<template slot="title">
|
||||||
</template>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</template>
|
||||||
</a-tooltip>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</span>
|
</a-tooltip>
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
</span>
|
||||||
</a-form-item>
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<span slot="label">
|
<br>
|
||||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-tooltip>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
<template slot="title">
|
</a-form-item>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<br>
|
||||||
</template>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-tooltip>
|
</a-form-item>
|
||||||
</span>
|
<a-form-item v-else>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<span slot="label">
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<a-tooltip>
|
||||||
</a-form-item>
|
<template slot="title">
|
||||||
</a-collapse-panel>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</a-collapse>
|
</template>
|
||||||
<a-collapse v-else>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
|
</a-tooltip>
|
||||||
<table width="100%">
|
</span>
|
||||||
<tr class="client-table-header">
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
</tr>
|
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
</a-form-item>
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
</a-collapse-panel>
|
||||||
</tr>
|
</a-collapse>
|
||||||
</table>
|
<a-collapse v-else>
|
||||||
</a-collapse-panel>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
||||||
</a-collapse>
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,38 +1,46 @@
|
|||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;">
|
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="none">None(Not Camouflage)</a-select-option>
|
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP(Camouflage Video Call)</a-select-option>
|
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
||||||
<a-select-option value="utp">UTP(Camouflage BT Download)</a-select-option>
|
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
||||||
<a-select-option value="wechat-video">Wechat-Video(Camouflage WeChat Video)</a-select-option>
|
<a-select-option value="wechat-video">Wechat-Video (Camouflage WeChat Video)</a-select-option>
|
||||||
<a-select-option value="dtls">DTLS(Camouflage DTLS 1.2 Packages)</a-select-option>
|
<a-select-option value="dtls">DTLS (Camouflage DTLS 1.2 Packages)</a-select-option>
|
||||||
<a-select-option value="wireguard">Wireguard(Camouflage Wireguard Packages)</a-select-option>
|
<a-select-option value="wireguard">Wireguard (Camouflage Wireguard Packages)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.number="inbound.stream.kcp.seed"></a-input>
|
<a-input v-model="inbound.stream.kcp.seed"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="MTU">
|
<a-form-item label="MTU">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="TTI (ms)">
|
<a-form-item label="TTI (ms)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.tti"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Uplink Capacity (MB/S)">
|
<a-form-item label="Uplink Capacity (MB/S)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.upCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Downlink Capacity (MB/S)">
|
<a-form-item label="Downlink Capacity (MB/S)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.downCap"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Congestion">
|
<a-form-item label="Congestion">
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Read Buffer Size (MB)">
|
<a-form-item label="Read Buffer Size (MB)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item label="Write Buffer Size (MB)">
|
<a-form-item label="Write Buffer Size (MB)">
|
||||||
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input>
|
<a-input-number v-model="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
</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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<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>
|
||||||
|
|||||||
@@ -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'">
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -100,6 +100,9 @@
|
|||||||
<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>
|
||||||
@@ -121,7 +124,7 @@
|
|||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<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-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
<a-button type="primary" icon="import" @click="setDefaultCertXtls">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
@@ -140,7 +143,7 @@
|
|||||||
</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"
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<tr v-if="infoModal.clientSettings.tgId">
|
||||||
<td>Telegram Username</td>
|
<td>Telegram ID</td>
|
||||||
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -103,6 +104,10 @@
|
|||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
|
setDefaultCertXtls(){
|
||||||
|
inModal.inbound.stream.xtls.certs[0].certFile = app.defaultCert;
|
||||||
|
inModal.inbound.stream.xtls.certs[0].keyFile = app.defaultKey;
|
||||||
|
},
|
||||||
async getNewX25519Cert(){
|
async getNewX25519Cert(){
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
|
|||||||
@@ -93,12 +93,13 @@
|
|||||||
</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="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<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>
|
||||||
@@ -115,15 +116,11 @@
|
|||||||
<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="siderDrawer.theme">
|
||||||
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
|
||||||
<a-icon type="qrcode"></a-icon>
|
|
||||||
{{ i18n "qrCode" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="edit">
|
<a-menu-item key="edit">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -155,7 +152,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">
|
||||||
@@ -167,7 +164,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
||||||
@@ -230,7 +227,7 @@
|
|||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-table
|
<a-table
|
||||||
v-else-if="record.protocol === Protocols.TROJAN"
|
v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS"
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerTrojanColumns"
|
:columns="innerTrojanColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
@@ -263,7 +260,7 @@
|
|||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 40,
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -298,7 +295,7 @@
|
|||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
@@ -307,11 +304,11 @@
|
|||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'Password', width: 120, dataIndex: "password" },
|
{ title: 'Password', width: 170, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -330,6 +327,7 @@
|
|||||||
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,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -337,11 +335,13 @@
|
|||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
|
this.refreshing = true;
|
||||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
const msg = await HttpUtil.post('/xui/inbound/list');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
|
this.refreshing = false;
|
||||||
},
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||||
@@ -478,7 +478,7 @@
|
|||||||
},
|
},
|
||||||
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" }}',
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
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 +527,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,
|
||||||
@@ -667,7 +667,7 @@
|
|||||||
},
|
},
|
||||||
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"}}',
|
||||||
@@ -682,9 +682,17 @@
|
|||||||
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const link = dbInbound.genLink(clientIndex);
|
const link = dbInbound.genLink(clientIndex);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
||||||
@@ -703,7 +711,7 @@
|
|||||||
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);
|
||||||
},
|
},
|
||||||
@@ -715,11 +723,13 @@
|
|||||||
},
|
},
|
||||||
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) {
|
||||||
@@ -817,6 +827,13 @@
|
|||||||
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) {
|
||||||
|
|||||||
@@ -111,9 +111,9 @@
|
|||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
</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="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
@@ -201,6 +202,7 @@
|
|||||||
</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="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
@@ -227,10 +229,28 @@
|
|||||||
{{ 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="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||||
|
@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" 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 "textModal"}}
|
{{template "textModal"}}
|
||||||
@@ -339,6 +359,29 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const backupModal = {
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
exportText: '',
|
||||||
|
importText: '',
|
||||||
|
show({
|
||||||
|
title = '{{ i18n "pages.index.backupTitle" }}',
|
||||||
|
description = '{{ i18n "pages.index.backupDescription" }}',
|
||||||
|
exportText = '{{ i18n "pages.index.exportDatabase" }}',
|
||||||
|
importText = '{{ i18n "pages.index.importDatabase" }}',
|
||||||
|
}) {
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.exportText = exportText;
|
||||||
|
this.importText = importText;
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -347,6 +390,7 @@
|
|||||||
status: new Status(),
|
status: new Status(),
|
||||||
versionModal,
|
versionModal,
|
||||||
logModal,
|
logModal,
|
||||||
|
backupModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
},
|
},
|
||||||
@@ -382,13 +426,12 @@
|
|||||||
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 +440,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');
|
||||||
@@ -413,20 +455,60 @@
|
|||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj,rows);
|
logModal.show(msg.obj, rows);
|
||||||
},
|
},
|
||||||
async openConfig(){
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.post('server/getConfigJson');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
|
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
||||||
},
|
},
|
||||||
getBackup(){
|
openBackup() {
|
||||||
|
backupModal.show({
|
||||||
|
title: '{{ i18n "pages.index.backupTitle" }}',
|
||||||
|
description: '{{ i18n "pages.index.backupDescription" }}',
|
||||||
|
exportText: '{{ i18n "pages.index.exportDatabase" }}',
|
||||||
|
importText: '{{ i18n "pages.index.importDatabase" }}',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportDatabase() {
|
||||||
window.location = basePath + 'server/getDb';
|
window.location = basePath + 'server/getDb';
|
||||||
}
|
},
|
||||||
|
importDatabase() {
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = '.db';
|
||||||
|
fileInput.addEventListener('change', async (event) => {
|
||||||
|
const dbFile = event.target.files[0];
|
||||||
|
if (dbFile) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('db', dbFile);
|
||||||
|
backupModal.hide();
|
||||||
|
this.loading(true);
|
||||||
|
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.loading(false);
|
||||||
|
if (!uploadMsg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading(true);
|
||||||
|
const restartMsg = await HttpUtil.post("/xui/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
if (restartMsg.success) {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(5000);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileInput.click();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -33,21 +33,21 @@
|
|||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.setting.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.setting.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
<a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''" >
|
||||||
<a-tabs default-active-key="1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
||||||
<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;'">
|
<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="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.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="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></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.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.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.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.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="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.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.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.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.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.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>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
@@ -74,148 +74,152 @@
|
|||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="2" tab='{{ i18n "pages.setting.userSetting"}}'>
|
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
||||||
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
<a-tabs default-active-key="sec-1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<a-form-item label='{{ i18n "pages.setting.oldUsername"}}'>
|
<a-tab-pane key="sec-1" tab='{{ i18n "pages.settings.security.admin"}}'>
|
||||||
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
||||||
</a-form-item>
|
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||||
<a-form-item label='{{ i18n "pages.setting.currentPassword"}}'>
|
<a-input v-model="user.oldUsername" style="max-width: 300px"></a-input>
|
||||||
<a-input type="password" v-model="user.oldPassword" style="max-width: 300px"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
||||||
<a-form-item label='{{ i18n "pages.setting.newUsername"}}'>
|
<a-input type="password" v-model="user.oldPassword" style="max-width: 300px"></a-input>
|
||||||
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
||||||
<a-form-item label='{{ i18n "pages.setting.newPassword"}}'>
|
<a-input v-model="user.newUsername" style="max-width: 300px"></a-input>
|
||||||
<a-input type="password" v-model="user.newPassword" style="max-width: 300px"></a-input>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
||||||
<a-form-item>
|
<a-input type="password" v-model="user.newPassword" style="max-width: 300px"></a-input>
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item>
|
||||||
</a-form>
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
</a-form-item>
|
||||||
<a-list-item style="padding: 20px">
|
</a-form>
|
||||||
<a-row>
|
</a-tab-pane>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-tab-pane key="sec-2" tab='{{ i18n "pages.settings.security.secret"}}'>
|
||||||
<a-list-item-meta title='{{ i18n "pages.setting.loginSecurity" }}' description='{{ i18n "pages.setting.loginSecurityDesc" }}'/>
|
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
|
||||||
</a-col>
|
<a-list-item style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-row>
|
||||||
<template>
|
<a-col :lg="24" :xl="12">
|
||||||
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
<a-list-item-meta title='{{ i18n "pages.settings.security.loginSecurity" }}' description='{{ i18n "pages.settings.security.loginSecurityDesc" }}'/>
|
||||||
</template>
|
</a-col>
|
||||||
</a-col>
|
<a-col :lg="24" :xl="12">
|
||||||
</a-row>
|
<template>
|
||||||
</a-list-item>
|
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
||||||
<a-list-item style="padding: 20px">
|
</template>
|
||||||
<a-row>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
</a-row>
|
||||||
<a-list-item-meta title='{{ i18n "pages.setting.secretToken" }}' description='{{ i18n "pages.setting.secretTokenDesc" }}'/>
|
</a-list-item>
|
||||||
|
<a-list-item style="padding: 20px">
|
||||||
</a-col>
|
<a-row>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<svg
|
<a-list-item-meta title='{{ i18n "pages.settings.security.secretToken" }}' description='{{ i18n "pages.settings.security.secretTokenDesc" }}'/>
|
||||||
@click="getNewSecret"
|
</a-col>
|
||||||
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"/>
|
<a-col :lg="24" :xl="12">
|
||||||
</svg>
|
<svg
|
||||||
<template>
|
@click="getNewSecret"
|
||||||
<a-textarea type="text" id='token' :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
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"/>
|
||||||
</template>
|
</svg>
|
||||||
</a-col>
|
<template>
|
||||||
</a-row>
|
<a-textarea type="text" id='token' :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
||||||
</a-list-item>
|
</template>
|
||||||
<a-button type="primary" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
</a-col>
|
||||||
</a-form>
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<a-button type="primary" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
|
||||||
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.xrayConfiguration"}}'>
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
<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-divider style="padding: 20px;">{{ i18n "pages.settings.actions"}}</a-divider>
|
||||||
<a-space direction="horizontal" style="padding: 0 20px">
|
<a-space direction="horizontal" style="padding: 0px 20px">
|
||||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
<a-divider style="padding: 20px;">{{ i18n "pages.settings.templates.title"}} </a-divider>
|
||||||
|
|
||||||
<a-divider>{{ i18n "pages.setting.basicTemplate"}}</a-divider>
|
<a-tabs default-active-key="tpl-1" :class="siderDrawer.isDarkTheme ? darkClass : ''" style="padding: 20px 20px;">
|
||||||
<a-collapse>
|
<a-tab-pane key="tpl-1" tab='{{ i18n "pages.settings.templates.basicTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.generalConfigs"}}'>
|
<a-collapse>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.generalConfigs"}}'>
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||||
{{ i18n "pages.setting.generalConfigsDesc" }}
|
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||||
</h2>
|
{{ i18n "pages.settings.templates.generalConfigsDesc" }}
|
||||||
</a-row>
|
</h2>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
</a-row>
|
||||||
<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.settings.templates.xrayConfigTorrent"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></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.settings.templates.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPorn"}}' desc='{{ i18n "pages.setting.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigAds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigAdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigPorn"}}' desc='{{ i18n "pages.settings.templates.xrayConfigPornDesc"}}' v-model="PornSettings"></setting-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.countryConfigs"}}'>
|
</a-collapse-panel>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.countryConfigs"}}'>
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||||
{{ i18n "pages.setting.countryConfigsDesc" }}
|
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||||
</h2>
|
{{ i18n "pages.settings.templates.countryConfigsDesc" }}
|
||||||
</a-row>
|
</h2>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRIp"}}' desc='{{ i18n "pages.setting.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
</a-row>
|
||||||
<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.settings.templates.xrayConfigIRIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRIpDesc"}}' v-model="IRIpSettings"></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.settings.templates.xrayConfigIRDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigIRDomainDesc"}}' v-model="IRDomainSettings"></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.settings.templates.xrayConfigChinaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaIpDesc"}}' v-model="ChinaIpSettings"></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.settings.templates.xrayConfigChinaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.setting.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaIp"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigRussiaDomain"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.ipv4Configs"}}'>
|
</a-collapse-panel>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.ipv4Configs"}}'>
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||||
{{ i18n "pages.setting.ipv4ConfigsDesc" }}
|
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||||
</h2>
|
{{ i18n "pages.settings.templates.ipv4ConfigsDesc" }}
|
||||||
</a-row>
|
</h2>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
</a-row>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.warpConfigs"}}'>
|
</a-collapse-panel>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.warpConfigs"}}'>
|
||||||
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
<h2 style="color: inherit; font-weight: bold; font-size: 18px; padding: 10px 20px; border-bottom: 2px solid;">
|
||||||
{{ i18n "pages.setting.warpConfigsDesc" }}
|
<a-icon type="warning" style="color: inherit; font-size: 24px;"></a-icon>
|
||||||
</h2>
|
{{ i18n "pages.settings.templates.warpConfigsDesc" }}
|
||||||
</a-row>
|
</h2>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.setting.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
|
</a-row>
|
||||||
<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.settings.templates.xrayConfigGoogleWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleWARPDesc"}}' v-model="GoogleWARPSettings"></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.settings.templates.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.settings.templates.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
</a-collapse>
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
</a-tab-pane>
|
||||||
<a-collapse>
|
<a-tab-pane key="tpl-2" tab='{{ i18n "pages.settings.templates.advancedTemplate"}}' style="padding-top: 20px;">
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigInbounds"}}'>
|
<a-collapse>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}'>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigInbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigInboundsDesc"}}' v-model="inboundSettings"></setting-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigOutbounds"}}'>
|
</a-collapse-panel>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}'>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigOutbounds"}}' desc='{{ i18n "pages.settings.templates.xrayConfigOutboundsDesc"}}' v-model="outboundSettings"></setting-list-item>
|
||||||
<a-collapse-panel header='{{ i18n "pages.setting.xrayConfigRoutings"}}'>
|
</a-collapse-panel>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
<a-collapse-panel header='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}'>
|
||||||
</a-collapse-panel>
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigRoutings"}}' desc='{{ i18n "pages.settings.templates.xrayConfigRoutingsDesc"}}' v-model="routingRuleSettings"></setting-list-item>
|
||||||
</a-collapse>
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
</a-tab-pane>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.settings.templates.completeTemplate"}}' style="padding-top: 20px;">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.xrayConfigTemplate"}}' desc='{{ i18n "pages.settings.templates.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="4" tab='{{ i18n "pages.setting.TGReminder"}}'>
|
<a-tab-pane key="4" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-list item-layout="horizontal" :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65);': 'background: white;'">
|
<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="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.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.settings.telegramToken"}}' desc='{{ i18n "pages.settings.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.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.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="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.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="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.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>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||||
</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-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
@@ -294,9 +298,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true , obj) {
|
loading(spinning = true, obj) {
|
||||||
if(obj == null)
|
if (obj == null) this.spinning = spinning;
|
||||||
this.spinning = spinning;
|
|
||||||
},
|
},
|
||||||
async getAllSetting() {
|
async getAllSetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
@@ -323,13 +326,14 @@
|
|||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.user = {};
|
this.user = {};
|
||||||
|
window.location.replace("/logout")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async restartPanel() {
|
async restartPanel() {
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.setting.restartPanel" }}',
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
content: '{{ i18n "pages.setting.restartPanelDesc" }}',
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
okText: '{{ i18n "sure" }}',
|
okText: '{{ i18n "sure" }}',
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => resolve(),
|
onOk: () => resolve(),
|
||||||
@@ -344,41 +348,40 @@
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getUserSecret(){
|
async getUserSecret() {
|
||||||
const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
|
const user_msg = await HttpUtil.post("/xui/setting/getUserSecret", this.user);
|
||||||
if (user_msg.success){
|
if (user_msg.success) {
|
||||||
this.user = user_msg.obj;
|
this.user = user_msg.obj;
|
||||||
}
|
}
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async updateSecret(){
|
async updateSecret() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
|
const msg = await HttpUtil.post("/xui/setting/updateUserSecret", this.user);
|
||||||
if (msg.success){
|
if (msg.success) {
|
||||||
this.user = msg.obj;
|
this.user = msg.obj;
|
||||||
}
|
window.location.replace("/logout")
|
||||||
this.loading(false);
|
}
|
||||||
await this.updateAllSetting();
|
this.loading(false);
|
||||||
},
|
await this.updateAllSetting();
|
||||||
async getNewSecret(){
|
},
|
||||||
this.loading(true);
|
async getNewSecret() {
|
||||||
await PromiseUtil.sleep(1000);
|
this.loading(true);
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
await PromiseUtil.sleep(1000);
|
||||||
var string = '';
|
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
var len = 64;
|
var string = "";
|
||||||
for(var ii=0; ii<len; ii++){
|
var len = 64;
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
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.user.loginSecret = string;
|
||||||
this.loading(false);
|
document.getElementById("token").value = this.user.loginSecret;
|
||||||
},
|
this.loading(false);
|
||||||
async toggleToken(value){
|
},
|
||||||
if(value)
|
async toggleToken(value) {
|
||||||
this.getNewSecret();
|
if (value) this.getNewSecret();
|
||||||
else
|
else this.user.loginSecret = "";
|
||||||
this.user.loginSecret = "";
|
},
|
||||||
},
|
|
||||||
async resetXrayConfigToDefault() {
|
async resetXrayConfigToDefault() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
||||||
@@ -332,6 +332,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
client_key = "password"
|
client_key = "password"
|
||||||
}
|
}
|
||||||
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
client_key = "email"
|
||||||
|
}
|
||||||
|
|
||||||
inerfaceClients := settings["clients"].([]interface{})
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
@@ -398,6 +401,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
oldClientId = oldClient.Email
|
||||||
} else {
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
}
|
}
|
||||||
@@ -595,6 +600,7 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
@@ -605,7 +611,8 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
|
||||||
|
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
DELETE FROM client_traffics
|
DELETE FROM client_traffics
|
||||||
@@ -616,6 +623,7 @@ func (s *InboundService) RemoveOrphanedTraffics() {
|
|||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -634,6 +642,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -664,6 +673,200 @@ func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
|
|||||||
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
|
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.ClientTraffic, inbound *model.Inbound, err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(traffics) > 0 {
|
||||||
|
inbound, err = s.GetInbound(traffics[0].InboundId)
|
||||||
|
return traffics[0], inbound, err
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return false, common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.getClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
clientOldEnabled := false
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
clientOldEnabled = oldClient.Enable
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["enable"] = !clientOldEnabled
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return !clientOldEnabled, s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) error {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.getClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["limitIp"] = count
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry_time int64) error {
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if inbound == nil {
|
||||||
|
return common.NewError("Inbound Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients, err := s.getClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ""
|
||||||
|
|
||||||
|
for _, oldClient := range oldClients {
|
||||||
|
if oldClient.Email == clientEmail {
|
||||||
|
if inbound.Protocol == "trojan" {
|
||||||
|
clientId = oldClient.Password
|
||||||
|
} else {
|
||||||
|
clientId = oldClient.ID
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clientId) == 0 {
|
||||||
|
return common.NewError("Client Not Found For Email:", clientEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["expiryTime"] = expiry_time
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
return s.UpdateInboundClient(inbound, clientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
|
Where("email = ?", clientEmail).
|
||||||
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
|
err := result.Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
@@ -972,3 +1175,8 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
// Remove orphaned traffics
|
// Remove orphaned traffics
|
||||||
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) MigrateDB() {
|
||||||
|
s.MigrationRequirements()
|
||||||
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -14,7 +15,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
"x-ui/util/sys"
|
"x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
@@ -73,7 +76,8 @@ type Release struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
xrayService XrayService
|
xrayService XrayService
|
||||||
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
@@ -395,6 +399,106 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
|||||||
return fileContents, nil
|
return fileContents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) ImportDB(file multipart.File) error {
|
||||||
|
// Check if the file is a SQLite database
|
||||||
|
isValidDb, err := database.IsSQLiteDB(file)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error checking db file format: %v", err)
|
||||||
|
}
|
||||||
|
if !isValidDb {
|
||||||
|
return common.NewError("Invalid db file format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the file reader to the beginning
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error resetting file reader: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the file as temporary file
|
||||||
|
tempPath := fmt.Sprintf("%s.temp", config.GetDBPath())
|
||||||
|
// Remove the existing fallback file (if any) before creating one
|
||||||
|
_, err = os.Stat(tempPath)
|
||||||
|
if err == nil {
|
||||||
|
errRemove := os.Remove(tempPath)
|
||||||
|
if errRemove != nil {
|
||||||
|
return common.NewErrorf("Error removing existing temporary db file: %v", errRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the temporary file
|
||||||
|
tempFile, err := os.Create(tempPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error creating temporary db file: %v", err)
|
||||||
|
}
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
// Remove temp file before returning
|
||||||
|
defer os.Remove(tempPath)
|
||||||
|
|
||||||
|
// Save uploaded file to temporary file
|
||||||
|
_, err = io.Copy(tempFile, file)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error saving db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can init db or not
|
||||||
|
err = database.InitDB(tempPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error checking db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop Xray
|
||||||
|
s.StopXrayService()
|
||||||
|
|
||||||
|
// Backup the current database for fallback
|
||||||
|
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
||||||
|
// Remove the existing fallback file (if any)
|
||||||
|
_, err = os.Stat(fallbackPath)
|
||||||
|
if err == nil {
|
||||||
|
errRemove := os.Remove(fallbackPath)
|
||||||
|
if errRemove != nil {
|
||||||
|
return common.NewErrorf("Error removing existing fallback db file: %v", errRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Move the current database to the fallback location
|
||||||
|
err = os.Rename(config.GetDBPath(), fallbackPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Error backing up temporary db file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the temporary file before returning
|
||||||
|
defer os.Remove(fallbackPath)
|
||||||
|
|
||||||
|
// Move temp to DB path
|
||||||
|
err = os.Rename(tempPath, config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
errRename := os.Rename(fallbackPath, config.GetDBPath())
|
||||||
|
if errRename != nil {
|
||||||
|
return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename)
|
||||||
|
}
|
||||||
|
return common.NewErrorf("Error moving db file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate DB
|
||||||
|
err = database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
errRename := os.Rename(fallbackPath, config.GetDBPath())
|
||||||
|
if errRename != nil {
|
||||||
|
return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename)
|
||||||
|
}
|
||||||
|
return common.NewErrorf("Error migrating db: %v", err)
|
||||||
|
}
|
||||||
|
s.inboundService.MigrateDB()
|
||||||
|
|
||||||
|
// Start Xray
|
||||||
|
err = s.RestartXrayService()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("Imported DB but Failed to start Xray: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||||
// Run the command
|
// Run the command
|
||||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
|||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email)
|
||||||
|
case "shadowsocks":
|
||||||
|
return s.genShadowsocksLink(inbound, email)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -105,9 +107,10 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
obj := map[string]interface{}{
|
obj := map[string]interface{}{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"ps": email,
|
"ps": remark,
|
||||||
"add": s.address,
|
"add": s.address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@@ -357,6 +360,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -380,7 +386,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = email
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,6 +541,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -558,10 +568,33 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = email
|
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
||||||
|
url.Fragment = remark
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
clients, _ := s.inboundService.getClients(inbound)
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
inboundPassword := settings["password"].(string)
|
||||||
|
method := settings["method"].(string)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
|
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email)
|
||||||
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
switch val := data.(type) {
|
switch val := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Tgbot struct {
|
|||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
serverService ServerService
|
serverService ServerService
|
||||||
|
xrayService XrayService
|
||||||
lastStatus *Status
|
lastStatus *Status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +149,170 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
||||||
|
|
||||||
|
if isAdmin {
|
||||||
|
dataArray := strings.Split(callbackQuery.Data, " ")
|
||||||
|
if len(dataArray) >= 2 && len(dataArray[1]) > 0 {
|
||||||
|
email := dataArray[1]
|
||||||
|
switch dataArray[0] {
|
||||||
|
case "client_refresh":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Client Refreshed successfully.", email))
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "client_cancel":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("❌ %s : Operation canceled.", email))
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "ips_refresh":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : IPs Refreshed successfully.", email))
|
||||||
|
t.searchClientIps(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "ips_cancel":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("❌ %s : Operation canceled.", email))
|
||||||
|
t.searchClientIps(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "reset_traffic":
|
||||||
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("❌ Cancel Reset", "client_cancel "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("✅ Confirm Reset Traffic?", "reset_traffic_c "+email),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
t.editMessageCallbackTgBot(callbackQuery.From.ID, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
|
case "reset_traffic_c":
|
||||||
|
err := t.inboundService.ResetClientTrafficByEmail(email)
|
||||||
|
if err == nil {
|
||||||
|
t.xrayService.SetToNeedRestart()
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Traffic reset successfully.", email))
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
} else {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, "❗ Error in Operation.")
|
||||||
|
}
|
||||||
|
case "reset_exp":
|
||||||
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("❌ Cancel Reset", "client_cancel "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("♾ Unlimited", "reset_exp_c "+email+" 0"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("1 Month", "reset_exp_c "+email+" 30"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("2 Months", "reset_exp_c "+email+" 60"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("3 Months", "reset_exp_c "+email+" 90"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("6 Months", "reset_exp_c "+email+" 180"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("9 Months", "reset_exp_c "+email+" 270"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("12 Months", "reset_exp_c "+email+" 360"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("10 Days", "reset_exp_c "+email+" 10"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("20 Days", "reset_exp_c "+email+" 20"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
t.editMessageCallbackTgBot(callbackQuery.From.ID, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
|
case "reset_exp_c":
|
||||||
|
if len(dataArray) == 3 {
|
||||||
|
days, err := strconv.Atoi(dataArray[2])
|
||||||
|
if err == nil {
|
||||||
|
var date int64 = 0
|
||||||
|
if days > 0 {
|
||||||
|
date = int64(-(days * 24 * 60 * 60000))
|
||||||
|
}
|
||||||
|
err := t.inboundService.ResetClientExpiryTimeByEmail(email, date)
|
||||||
|
if err == nil {
|
||||||
|
t.xrayService.SetToNeedRestart()
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Expire days reset successfully.", email))
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, "❗ Error in Operation.")
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "ip_limit":
|
||||||
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("❌ Cancel IP Limit", "client_cancel "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("♾ Unlimited", "ip_limit_c "+email+" 0"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("1", "ip_limit_c "+email+" 1"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("2", "ip_limit_c "+email+" 2"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("3", "ip_limit_c "+email+" 3"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("4", "ip_limit_c "+email+" 4"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("5", "ip_limit_c "+email+" 5"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("6", "ip_limit_c "+email+" 6"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("7", "ip_limit_c "+email+" 7"),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("8", "ip_limit_c "+email+" 8"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("9", "ip_limit_c "+email+" 9"),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("10", "ip_limit_c "+email+" 10"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
t.editMessageCallbackTgBot(callbackQuery.From.ID, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
|
case "ip_limit_c":
|
||||||
|
if len(dataArray) == 3 {
|
||||||
|
count, err := strconv.Atoi(dataArray[2])
|
||||||
|
if err == nil {
|
||||||
|
err := t.inboundService.ResetClientIpLimitByEmail(email, count)
|
||||||
|
if err == nil {
|
||||||
|
t.xrayService.SetToNeedRestart()
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : IP limit %d saved successfully.", email, count))
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, "❗ Error in Operation.")
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
case "clear_ips":
|
||||||
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("❌ Cancel", "ips_cancel "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("✅ Confirm Clear IPs?", "clear_ips_c "+email),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
t.editMessageCallbackTgBot(callbackQuery.From.ID, callbackQuery.Message.MessageID, inlineKeyboard)
|
||||||
|
case "clear_ips_c":
|
||||||
|
err := t.inboundService.ClearClientIps(email)
|
||||||
|
if err == nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : IPs cleared successfully.", email))
|
||||||
|
t.searchClientIps(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
} else {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, "❗ Error in Operation.")
|
||||||
|
}
|
||||||
|
case "ip_log":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Get IP Log.", email))
|
||||||
|
t.searchClientIps(callbackQuery.From.ID, email)
|
||||||
|
case "toggle_enable":
|
||||||
|
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
|
||||||
|
if err == nil {
|
||||||
|
t.xrayService.SetToNeedRestart()
|
||||||
|
if enabled {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Enabled successfully.", email))
|
||||||
|
} else {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, fmt.Sprintf("✅ %s : Disabled successfully.", email))
|
||||||
|
}
|
||||||
|
t.searchClient(callbackQuery.From.ID, email, callbackQuery.Message.MessageID)
|
||||||
|
} else {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, "❗ Error in Operation.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Respond to the callback query, telling Telegram to show the user
|
// Respond to the callback query, telling Telegram to show the user
|
||||||
// a message with the data received.
|
// a message with the data received.
|
||||||
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
||||||
@@ -165,7 +330,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bo
|
|||||||
case "get_backup":
|
case "get_backup":
|
||||||
t.sendBackup(callbackQuery.From.ID)
|
t.sendBackup(callbackQuery.From.ID)
|
||||||
case "client_traffic":
|
case "client_traffic":
|
||||||
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName)
|
t.getClientUsage(callbackQuery.From.ID, callbackQuery.From.UserName, strconv.FormatInt(callbackQuery.From.ID, 10))
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
t.SendMsgToTgbot(callbackQuery.From.ID, "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UID|Password]</code>\r\n \r\nUse UID for vmess/vless and Password for Trojan.")
|
||||||
case "commands":
|
case "commands":
|
||||||
@@ -215,7 +380,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string) {
|
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string, inlineKeyboard ...tgbotapi.InlineKeyboardMarkup) {
|
||||||
var allMessages []string
|
var allMessages []string
|
||||||
limit := 2000
|
limit := 2000
|
||||||
// paging message if it is big
|
// paging message if it is big
|
||||||
@@ -236,6 +401,9 @@ func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string) {
|
|||||||
for _, message := range allMessages {
|
for _, message := range allMessages {
|
||||||
info := tgbotapi.NewMessage(tgid, message)
|
info := tgbotapi.NewMessage(tgid, message)
|
||||||
info.ParseMode = "HTML"
|
info.ParseMode = "HTML"
|
||||||
|
if len(inlineKeyboard) > 0 {
|
||||||
|
info.ReplyMarkup = inlineKeyboard[0]
|
||||||
|
}
|
||||||
_, err := bot.Send(info)
|
_, err := bot.Send(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error sending telegram message :", err)
|
logger.Warning("Error sending telegram message :", err)
|
||||||
@@ -362,13 +530,8 @@ func (t *Tgbot) getInboundUsages() string {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string) {
|
||||||
if len(tgUserName) == 0 {
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
||||||
msg := "Your configuration is not found!\nYou should configure your telegram username and ask Admin to add it to your configuration."
|
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
msg := "❌ Something went wrong!"
|
msg := "❌ Something went wrong!"
|
||||||
@@ -376,7 +539,21 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>"
|
if len(tgUserName) == 0 {
|
||||||
|
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram user id in your configuration(s).\n\nYour user id: <b>" + tgUserID + "</b>"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
traffics, err = t.inboundService.GetClientTrafficTgBot(tgUserName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
msg := "❌ Something went wrong!"
|
||||||
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
msg := "Your configuration is not found!\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\n\nYour username: <b>@" + tgUserName + "</b>\n\nYour user id: <b>" + tgUserID + "</b>"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -403,7 +580,28 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
t.SendAnswer(chatId, "Please choose:", false)
|
t.SendAnswer(chatId, "Please choose:", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
|
||||||
|
ips, err := t.inboundService.GetInboundClientIps(email)
|
||||||
|
if err != nil || len(ips) == 0 {
|
||||||
|
ips = "No IP Record"
|
||||||
|
}
|
||||||
|
output := fmt.Sprintf("📧 Email: %s\r\n🔢 IPs: \r\n%s\r\n", email, ips)
|
||||||
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("🔄 Refresh", "ips_refresh "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("❌ Clear IPs", "clear_ips "+email),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if len(messageID) > 0 {
|
||||||
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
||||||
|
} else {
|
||||||
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -433,7 +631,29 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
||||||
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||||
total, expiryTime)
|
total, expiryTime)
|
||||||
t.SendMsgToTgbot(chatId, output)
|
var inlineKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("🔄 Refresh", "client_refresh "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("📈 Reset Traffic", "reset_traffic "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("📅 Reset Expire Days", "reset_exp "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("🔢 IP Log", "ip_log "+email),
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("🔢 IP Limit", "ip_limit "+email),
|
||||||
|
),
|
||||||
|
tgbotapi.NewInlineKeyboardRow(
|
||||||
|
tgbotapi.NewInlineKeyboardButtonData("🔘 Enable / Disable", "toggle_enable "+email),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if len(messageID) > 0 {
|
||||||
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
||||||
|
} else {
|
||||||
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||||
@@ -608,3 +828,28 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
|||||||
logger.Warning("Error in uploading config.json: ", err)
|
logger.Warning("Error in uploading config.json: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
|
||||||
|
callback := tgbotapi.NewCallback(id, message)
|
||||||
|
if _, err := bot.Request(callback); err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard tgbotapi.InlineKeyboardMarkup) {
|
||||||
|
edit := tgbotapi.NewEditMessageReplyMarkup(chatId, messageID, inlineKeyboard)
|
||||||
|
if _, err := bot.Request(edit); err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...tgbotapi.InlineKeyboardMarkup) {
|
||||||
|
edit := tgbotapi.NewEditMessageText(chatId, messageID, text)
|
||||||
|
edit.ParseMode = "HTML"
|
||||||
|
if len(inlineKeyboard) > 0 {
|
||||||
|
edit.ReplyMarkup = &inlineKeyboard[0]
|
||||||
|
}
|
||||||
|
if _, err := bot.Request(edit); err != nil {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"unlimited" = "Unlimited"
|
"unlimited" = "Unlimited"
|
||||||
"none" = "None"
|
"none" = "None"
|
||||||
"qrCode" = "QR Code"
|
"qrCode" = "QR Code"
|
||||||
"info" = "More information"
|
"info" = "More Information"
|
||||||
"edit" = "Edit"
|
"edit" = "Edit"
|
||||||
"delete" = "Delete"
|
"delete" = "Delete"
|
||||||
"reset" = "Reset"
|
"reset" = "Reset"
|
||||||
@@ -43,29 +43,29 @@
|
|||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
"success" = " Success"
|
"success" = "Success"
|
||||||
"getVersion" = "Get version"
|
"getVersion" = "Get version"
|
||||||
"install" = "Install"
|
"install" = "Install"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
"usage" = "Usage"
|
"usage" = "Usage"
|
||||||
"secretToken" = "Secret token"
|
"secretToken" = "Secret Token"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"setting" = "Panel Setting"
|
"settings" = "Panel Settings"
|
||||||
"logout" = "Logout"
|
"logout" = "Logout"
|
||||||
"link" = "Other"
|
"link" = "Other"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"title" = "Login"
|
"title" = "Login"
|
||||||
"loginAgain" = "The login time limit has expired, please log in again"
|
"loginAgain" = "The login time limit has expired. Please log in again."
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
"invalidFormData" = "Input Data Format is Invalid"
|
"invalidFormData" = "Input data format is invalid."
|
||||||
"emptyUsername" = "Please Enter Username"
|
"emptyUsername" = "Please enter username."
|
||||||
"emptyPassword" = "Please Enter Password"
|
"emptyPassword" = "Please enter password."
|
||||||
"wrongUsernameOrPassword" = "Invalid username or password"
|
"wrongUsernameOrPassword" = "Invalid username or password."
|
||||||
"successLogin" = "Login"
|
"successLogin" = "Login"
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
@@ -81,21 +81,28 @@
|
|||||||
"operationHours" = "Operation Hours"
|
"operationHours" = "Operation Hours"
|
||||||
"operationHoursDesc" = "System uptime: time since startup."
|
"operationHoursDesc" = "System uptime: time since startup."
|
||||||
"systemLoad" = "System Load"
|
"systemLoad" = "System Load"
|
||||||
"connectionCount" = "Number of connections"
|
"connectionCount" = "Number of Connections"
|
||||||
"connectionCountDesc" = "Total connections across all network cards"
|
"connectionCountDesc" = "Total connections across all network cards."
|
||||||
"upSpeed" = "Total upload speed for all network cards"
|
"upSpeed" = "Total upload speed for all network cards."
|
||||||
"downSpeed" = "Total download speed for all network cards"
|
"downSpeed" = "Total download speed for all network cards."
|
||||||
"totalSent" = "Total upload traffic of all network cards since system startup"
|
"totalSent" = "Total upload traffic of all network cards since system startup."
|
||||||
"totalReceive" = "Total download data across all network cards since system startup"
|
"totalReceive" = "Total download data across all network cards since system startup."
|
||||||
"xraySwitchVersionDialog" = "Switch xray version"
|
"xraySwitchVersionDialog" = "Switch Xray Version"
|
||||||
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
"xraySwitchVersionDialogDesc" = "Are you sure you want to switch the Xray version to"
|
||||||
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
"dontRefresh" = "Installation is in progress, please do not refresh this page."
|
||||||
|
"logs" = "Logs"
|
||||||
|
"config" = "Config"
|
||||||
|
"backup" = "Backup & Restore"
|
||||||
|
"backupTitle" = "Backup & Restore Database"
|
||||||
|
"backupDescription" = "Remember to backup before importing a new database."
|
||||||
|
"exportDatabase" = "Download Database"
|
||||||
|
"importDatabase" = "Upload Database"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "Inbounds"
|
"title" = "Inbounds"
|
||||||
"totalDownUp" = "Total uploads/downloads"
|
"totalDownUp" = "Total Uploads/Downloads"
|
||||||
"totalUsage" = "Total usage"
|
"totalUsage" = "Total Usage"
|
||||||
"inboundCount" = "Number of inbound"
|
"inboundCount" = "Number of Inbounds"
|
||||||
"operate" = "Menu"
|
"operate" = "Menu"
|
||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
@@ -104,77 +111,79 @@
|
|||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Details"
|
"details" = "Details"
|
||||||
"transportConfig" = "Transport"
|
"transportConfig" = "Transport"
|
||||||
"expireDate" = "Expire date"
|
"expireDate" = "Expire Date"
|
||||||
"resetTraffic" = "Reset traffic"
|
"resetTraffic" = "Reset Traffic"
|
||||||
"addInbound" = "Add Inbound"
|
"addInbound" = "Add Inbound"
|
||||||
"generalActions" = "General Actions"
|
"generalActions" = "General Actions"
|
||||||
"addTo" = "Create"
|
"create" = "Create"
|
||||||
"revise" = "Update"
|
"update" = "Update"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify Inbound"
|
||||||
"deleteInbound" = "Delete Inbound"
|
"deleteInbound" = "Delete Inbound"
|
||||||
"deleteInboundContent" = "Confirm deletion of inbound?"
|
"deleteInboundContent" = "Confirm deletion of inbound?"
|
||||||
"resetTrafficContent" = "Confirm traffic reset?"
|
"resetTrafficContent" = "Confirm traffic reset?"
|
||||||
"copyLink" = "Copy Link"
|
"copyLink" = "Copy Link"
|
||||||
"address" = "Address"
|
"address" = "Address"
|
||||||
"network" = "Network"
|
"network" = "Network"
|
||||||
"destinationPort" = "Destination port"
|
"destinationPort" = "Destination Port"
|
||||||
"targetAddress" = "Target address"
|
"targetAddress" = "Target Address"
|
||||||
"disableInsecureEncryption" = "Disable insecure encryption"
|
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "Means no limit"
|
"meansNoLimit" = "Means No Limit"
|
||||||
"totalFlow" = "Total flow"
|
"totalFlow" = "Total Flow"
|
||||||
"leaveBlankToNeverExpire" = "Leave blank to set no expiration"
|
"leaveBlankToNeverExpire" = "Leave Blank to Never Expire"
|
||||||
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
|
"noRecommendKeepDefault" = "No special requirements to maintain default settings"
|
||||||
"certificatePath" = "Certificate file path"
|
"certificatePath" = "Certificate File Path"
|
||||||
"certificateContent" = "Certificate file content"
|
"certificateContent" = "Certificate File Content"
|
||||||
"publicKeyPath" = "Public key path"
|
"publicKeyPath" = "Public Key Path"
|
||||||
"publicKeyContent" = "Public key content"
|
"publicKeyContent" = "Public Key Content"
|
||||||
"keyPath" = "Private Key path"
|
"keyPath" = "Private Key Path"
|
||||||
"keyContent" = "Private Key content"
|
"keyContent" = "Private Key Content"
|
||||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
"client" = "Client"
|
"client" = "Client"
|
||||||
"export" = "Export links"
|
"export" = "Export Links"
|
||||||
"Clone" = "Clone"
|
"clone" = "Clone"
|
||||||
"cloneInbound" = "Create"
|
"cloneInbound" = "Clone"
|
||||||
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone"
|
"cloneInboundContent" = "All settings of this inbound, except for Port, Listening IP, and Clients, will be applied to the clone."
|
||||||
"cloneInboundOk" = "Creating a clone from"
|
"cloneInboundOk" = "Clone"
|
||||||
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
||||||
"resetAllTrafficOkText" = "Confirm"
|
"resetAllTrafficOkText" = "Confirm"
|
||||||
"resetAllTrafficCancelText" = "Cancel"
|
"resetAllTrafficCancelText" = "Cancel"
|
||||||
"IPLimit" = "IP Limit"
|
"IPLimit" = "IP Limit"
|
||||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (Enter 0 to disable IP limit)"
|
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
||||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
"resetInboundClientTrafficTitle" = "Reset all clients traffic"
|
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
||||||
"resetInboundClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
||||||
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
||||||
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of all clients ?"
|
"resetAllClientTrafficContent" = "Are you sure you want to reset all traffics for all clients?"
|
||||||
"delDepletedClients" = "Delete depleted clients"
|
"delDepletedClients" = "Delete Depleted Clients"
|
||||||
"delDepletedClientsTitle" = "Delete depleted clients"
|
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||||
"delDepletedClientsContent" = "Are you sure to delete all depleted clients ?"
|
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
|
||||||
"Email" = "Email"
|
"Email" = "Email"
|
||||||
"EmailDesc" = "Please provide a unique email address"
|
"EmailDesc" = "Please provide a unique email address."
|
||||||
"IPLimitlog" = "IP Log"
|
"IPLimitlog" = "IP Log"
|
||||||
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
"IPLimitlogDesc" = "IPs history log (before enabling inbound after it has been disabled by IP limit, you should clear the log)."
|
||||||
"IPLimitlogclear" = "Clear The Log"
|
"IPLimitlogclear" = "Clear The Log"
|
||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"XTLSdec" = "Xray core needs to be 1.7.5"
|
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
||||||
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
||||||
|
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )"
|
||||||
|
"subscriptionDesc" = "you can find your sub link on Details, also ou can use the same name for several configurations"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "Add client"
|
"add" = "Add Client"
|
||||||
"edit" = "Edit client"
|
"edit" = "Edit Client"
|
||||||
"submitAdd" = "Add client"
|
"submitAdd" = "Add Client"
|
||||||
"submitEdit" = "Save changes"
|
"submitEdit" = "Save changes"
|
||||||
"clientCount" = "Number of clients"
|
"clientCount" = "Number of Clients"
|
||||||
"bulk" = "Add bulk"
|
"bulk" = "Add Bulk"
|
||||||
"method" = "Method"
|
"method" = "Method"
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
"last" = "Last"
|
"last" = "Last"
|
||||||
"prefix" = "Prefix"
|
"prefix" = "Prefix"
|
||||||
"postfix" = "postfix"
|
"postfix" = "Postfix"
|
||||||
"delayedStart" = "Start after first use"
|
"delayedStart" = "Start after first use"
|
||||||
"expireDays" = "Expire days"
|
"expireDays" = "Expire days"
|
||||||
"days" = "day(s)"
|
"days" = "day(s)"
|
||||||
@@ -199,113 +208,119 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "Encryption"
|
"encryption" = "Encryption"
|
||||||
|
|
||||||
[pages.setting]
|
[pages.settings]
|
||||||
"title" = "Setting"
|
"title" = "Settings"
|
||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
"restartPanel" = "Restart Panel"
|
"restartPanel" = "Restart Panel "
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
||||||
"actions" = "Actions"
|
"actions" = "Actions"
|
||||||
"resetDefaultConfig" = "Reset to default config"
|
"resetDefaultConfig" = "Reset to Default Configuration"
|
||||||
"panelConfig" = "Panel Configuration"
|
"panelSettings" = "Panel Settings"
|
||||||
"userSetting" = "User Setting"
|
"securitySettings" = "Security Settings"
|
||||||
"xrayConfiguration" = "Xray Configuration"
|
"xrayConfiguration" = "Xray Configuration"
|
||||||
"TGReminder" = "TG Reminder Related Settings"
|
"TGBotSettings" = "Telegram Bot Settings"
|
||||||
"otherSetting" = "Other Setting"
|
"panelListeningIP" = "Panel Listening IP"
|
||||||
"panelListeningIP" = "Panel listening IP"
|
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs. Restart the panel to apply changes."
|
||||||
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs, restart the panel to take effect"
|
|
||||||
"panelPort" = "Panel Port"
|
"panelPort" = "Panel Port"
|
||||||
"panelPortDesc" = "Restart the panel to take effect"
|
"panelPortDesc" = "Restart the panel to apply changes."
|
||||||
"publicKeyPath" = "Panel certificate public key file path"
|
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"publicKeyPathDesc" = "Fill in an absolute path starting with '/'. Restart the panel to apply changes."
|
||||||
"privateKeyPath" = "Panel certificate private key file path"
|
"privateKeyPath" = "Panel Certificate Private Key File Path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"privateKeyPathDesc" = "Fill in an absolute path starting with '/'. Restart the panel to apply changes."
|
||||||
"panelUrlPath" = "panel url root path"
|
"panelUrlPath" = "Panel URL Root Path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
"panelUrlPathDesc" = "Must start with '/' and end with '/'. Restart the panel to apply changes."
|
||||||
"oldUsername" = "Current Username"
|
"oldUsername" = "Current Username"
|
||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
"newUsername" = "New Username"
|
"newUsername" = "New Username"
|
||||||
"newPassword" = "New Password"
|
"newPassword" = "New Password"
|
||||||
"basicTemplate" = "Basic Template"
|
"telegramBotEnable" = "Enable Telegram bot"
|
||||||
"advancedTemplate" = "Advanced Template parts"
|
"telegramBotEnableDesc" = "Restart the panel to take effect."
|
||||||
"completeTemplate" = "Complete Template of Xray configuration"
|
|
||||||
"generalConfigs" = "General Configs"
|
|
||||||
"generalConfigsDesc" = "This options will prevent users from connecting to specific protocols and websites."
|
|
||||||
"countryConfigs" = "Country Configs"
|
|
||||||
"countryConfigsDesc" = "This options will prevent users from connecting to specific country domains."
|
|
||||||
"ipv4Configs" = "IPv4 Configs"
|
|
||||||
"ipv4ConfigsDesc" = "This options will be route to target domains only via IPv4."
|
|
||||||
"warpConfigs" = "WARP Configs"
|
|
||||||
"warpConfigsDesc" = "Caution: Before using this options, Install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
|
||||||
"xrayConfigTemplate" = "Xray Configuration Template"
|
|
||||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
|
||||||
"xrayConfigTorrent" = "Ban bittorrent usage"
|
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using bittorrent by users, restart the panel to take effect"
|
|
||||||
"xrayConfigPrivateIp" = "Ban private IP ranges to connect"
|
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigAds" = "Block Ads"
|
|
||||||
"xrayConfigAdsDesc" = "Change the configuration template to block Ads, restart the panel to take effect"
|
|
||||||
"xrayConfigPorn" = "Block Porn Websites"
|
|
||||||
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to Porn websites, restart the panel to take effect"
|
|
||||||
"xrayConfigIRIp" = "Ban Iran IP ranges to connect"
|
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigIRDomain" = "Ban Iran Domains to connect"
|
|
||||||
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains, restart the panel to take effect"
|
|
||||||
"xrayConfigChinaIp" = "Ban China IP ranges to connect"
|
|
||||||
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigChinaDomain" = "Ban China Domains to connect"
|
|
||||||
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains, restart the panel to take effect"
|
|
||||||
"xrayConfigRussiaIp" = "Ban Russia IP ranges to connect"
|
|
||||||
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges, restart the panel to take effect"
|
|
||||||
"xrayConfigRussiaDomain" = "Ban Russia Domains to connect"
|
|
||||||
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains, restart the panel to take effect"
|
|
||||||
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
|
||||||
"xrayConfigGoogleIPv4Desc" = "Add routing for google to connect with IPv4, restart the panel to take effect"
|
|
||||||
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
|
||||||
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4, restart the panel to take effect"
|
|
||||||
"xrayConfigGoogleWARP" = "Route Google to WARP"
|
|
||||||
"xrayConfigGoogleWARPDesc" = "Add routing for Google to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) to WARP"
|
|
||||||
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigNetflixWARP" = "Route Netflix to WARP"
|
|
||||||
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigSpotifyWARP" = "Route Spotify to WARP"
|
|
||||||
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify to WARP, restart the panel to take effect"
|
|
||||||
"xrayConfigIRWARP" = "Route Iran Domains to WARP"
|
|
||||||
"xrayConfigIRWARPDesc" = "Add routing for Iran Domains to WARP. restart the panel to take effect"
|
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration template to accept special clients, restart the panel to take effect"
|
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
|
||||||
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server, restart the panel to take effect"
|
|
||||||
"xrayConfigRoutings" = "Configuration of Routing rules"
|
|
||||||
"xrayConfigRoutingsDesc" = "Change the configuration template to define Routing rules for this server, restart the panel to take effect"
|
|
||||||
"telegramBotEnable" = "Enable telegram bot"
|
|
||||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
"telegramTokenDesc" = "Restart the panel to take effect"
|
"telegramTokenDesc" = "Restart the panel to take effect."
|
||||||
"telegramChatId" = "Telegram Admin ChatIds"
|
"telegramChatId" = "Telegram Admin Chat IDs"
|
||||||
"telegramChatIdDesc" = "Multi chatIDs separated by comma. Restart the panel to take effect"
|
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot to get your Chat IDs. Restart the panel to apply changes."
|
||||||
"telegramNotifyTime" = "Telegram bot notification time"
|
"telegramNotifyTime" = "Telegram bot notification time"
|
||||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
"telegramNotifyTimeDesc" = "Use Crontab timing format. Restart the panel to apply changes."
|
||||||
"tgNotifyBackup" = "Database backup"
|
"tgNotifyBackup" = "Database Backup"
|
||||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
"tgNotifyBackupDesc" = "Include database backup file with report notification. Restart the panel to apply changes."
|
||||||
"sessionMaxAge" = "Session maximum age"
|
"sessionMaxAge" = "Session maximum age"
|
||||||
"sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
|
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
||||||
"expireTimeDiff" = "Exhaustion time threshold"
|
"expireTimeDiff" = "Expiration threshold for notification"
|
||||||
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
"expireTimeDiffDesc" = "Get notified about account expiration before the threshold (unit: day)"
|
||||||
"trafficDiff" = "Exhaustion traffic threshold"
|
"trafficDiff" = "Traffic threshold for notification"
|
||||||
"trafficDiffDesc" = "Detect exhaustion before finishing traffic (unit:GB)"
|
"trafficDiffDesc" = "Get notified about traffic exhaustion before reaching the threshold (unit: GB)"
|
||||||
"tgNotifyCpu" = "CPU percentage alert threshold"
|
"tgNotifyCpu" = "CPU percentage alert threshold"
|
||||||
"tgNotifyCpuDesc" = "This telegram bot will send you a notification if CPU usage is more than this percentage (unit:%)"
|
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
||||||
"timeZonee" = "Time Zone"
|
"timeZone" = "Time zone"
|
||||||
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone. Restart the panel to apply changes."
|
||||||
"loginSecurity" = "Login security"
|
|
||||||
"loginSecurityDesc" = "Toggle additional step in user login page"
|
|
||||||
"secretToken" = "Secret Token"
|
|
||||||
"secretTokenDesc" = "Copy this secret token and keep it in a safe place; without this you won't be able to login. This can not be recovered from x-ui command tool neither"
|
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.settings.templates]
|
||||||
"modifySetting" = "Modify setting"
|
"title" = "Templates"
|
||||||
"getSetting" = "Get setting"
|
"basicTemplate" = "Basic Template"
|
||||||
"modifyUser" = "Modify user"
|
"advancedTemplate" = "Advanced Template"
|
||||||
"originalUserPassIncorrect" = "The original user name or original password is incorrect"
|
"completeTemplate" = "Complete Template"
|
||||||
|
"generalConfigs" = "General Configs"
|
||||||
|
"generalConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
|
||||||
|
"countryConfigs" = "Country Configs"
|
||||||
|
"countryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
|
||||||
|
"ipv4Configs" = "IPv4 Configs"
|
||||||
|
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
||||||
|
"warpConfigs" = "WARP Configs"
|
||||||
|
"warpConfigsDesc" = "Caution: Before using these options, install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
||||||
|
"xrayConfigTemplate" = "Xray Configuration Template"
|
||||||
|
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template. Restart the panel to apply changes."
|
||||||
|
"xrayConfigTorrent" = "Ban BitTorrent Usage"
|
||||||
|
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users. Restart the panel to apply changes."
|
||||||
|
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
|
||||||
|
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges. Restart the panel to apply changes."
|
||||||
|
"xrayConfigAds" = "Block Ads"
|
||||||
|
"xrayConfigAdsDesc" = "Change the configuration template to block ads. Restart the panel to apply changes."
|
||||||
|
"xrayConfigPorn" = "Block Porn Websites"
|
||||||
|
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to porn websites. Restart the panel to apply changes."
|
||||||
|
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
||||||
|
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges. Restart the panel to apply changes."
|
||||||
|
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
||||||
|
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains. Restart the panel to apply changes."
|
||||||
|
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
|
||||||
|
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges. Restart the panel to apply changes."
|
||||||
|
"xrayConfigChinaDomain" = "Disable connection to China domains"
|
||||||
|
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains. Restart the panel to apply changes."
|
||||||
|
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
|
||||||
|
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges. Restart the panel to apply changes."
|
||||||
|
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
|
||||||
|
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains. Restart the panel to apply changes."
|
||||||
|
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
||||||
|
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4. Restart the panel to apply changes."
|
||||||
|
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
||||||
|
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4. Restart the panel to apply changes."
|
||||||
|
"xrayConfigGoogleWARP" = "Route Google through WARP."
|
||||||
|
"xrayConfigGoogleWARPDesc" = "Add routing for Google via WARP. Restart the panel to apply changes."
|
||||||
|
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) through WARP."
|
||||||
|
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP. Restart the panel to apply changes."
|
||||||
|
"xrayConfigNetflixWARP" = "Route Netflix through WARP."
|
||||||
|
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix via WARP. Restart the panel to apply changes."
|
||||||
|
"xrayConfigSpotifyWARP" = "Route Spotify through WARP."
|
||||||
|
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify via WARP. Restart the panel to apply changes."
|
||||||
|
"xrayConfigIRWARP" = "Route Iran domains through WARP."
|
||||||
|
"xrayConfigIRWARPDesc" = "Add routing for Iran domains via WARP. Restart the panel to apply changes."
|
||||||
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
|
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients. Restart the panel to apply changes."
|
||||||
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
|
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server. Restart the panel to apply changes."
|
||||||
|
"xrayConfigRoutings" = "Configuration of routing rules."
|
||||||
|
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server. Restart the panel to apply changes."
|
||||||
|
|
||||||
|
[pages.settings.security]
|
||||||
|
"admin" = "Admin"
|
||||||
|
"secret" = "Secret Token"
|
||||||
|
"loginSecurity" = "Login security"
|
||||||
|
"loginSecurityDesc" = "Enable additional user login security step"
|
||||||
|
"secretToken" = "Secret Token"
|
||||||
|
"secretTokenDesc" = "Please copy and securely store this token in a safe place. This token is required for login and cannot be recovered from the x-ui command tool."
|
||||||
|
|
||||||
|
[pages.settings.toasts]
|
||||||
|
"modifySettings" = "Modify Settings "
|
||||||
|
"getSettings" = "Get Settings "
|
||||||
|
"modifyUser" = "Modify User "
|
||||||
|
"originalUserPassIncorrect" = "Incorrect original username or password"
|
||||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
"inbounds" = "سرویس ها"
|
"inbounds" = "سرویس ها"
|
||||||
"setting" = "تنظیمات پنل"
|
"settings" = "تنظیمات پنل"
|
||||||
"logout" = "خروج"
|
"logout" = "خروج"
|
||||||
"link" = "دیگر"
|
"link" = "دیگر"
|
||||||
|
|
||||||
@@ -89,7 +89,14 @@
|
|||||||
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
||||||
"xraySwitchVersionDialog" = "تغییر ورژن"
|
"xraySwitchVersionDialog" = "تغییر ورژن"
|
||||||
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
||||||
"dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید "
|
"dontRefresh" = "در حال نصب ، لطفا رفرش نکنید "
|
||||||
|
"logs" = "گزارش ها"
|
||||||
|
"config" = "تنظیمات"
|
||||||
|
"backup" = "پشتیبان گیری و بازیابی"
|
||||||
|
"backupTitle" = "پشتیبان گیری و بازیابی دیتابیس"
|
||||||
|
"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید."
|
||||||
|
"exportDatabase" = "دانلود دیتابیس"
|
||||||
|
"importDatabase" = "آپلود دیتابیس"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "کاربران"
|
"title" = "کاربران"
|
||||||
@@ -108,8 +115,8 @@
|
|||||||
"resetTraffic" = "ریست ترافیک"
|
"resetTraffic" = "ریست ترافیک"
|
||||||
"addInbound" = "اضافه کردن سرویس"
|
"addInbound" = "اضافه کردن سرویس"
|
||||||
"generalActions" = "عملیات کلی"
|
"generalActions" = "عملیات کلی"
|
||||||
"addTo" = "اضافه کردن"
|
"create" = "اضافه کردن"
|
||||||
"revise" = "ویرایش"
|
"update" = "ویرایش"
|
||||||
"modifyInbound" = "ویرایش سرویس"
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
"deleteInbound" = "حذف سرویس"
|
"deleteInbound" = "حذف سرویس"
|
||||||
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
||||||
@@ -134,7 +141,7 @@
|
|||||||
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
||||||
"client" = "کاربر"
|
"client" = "کاربر"
|
||||||
"export" = "استخراج لینکها"
|
"export" = "استخراج لینکها"
|
||||||
"Clone" = "شبیه سازی"
|
"clone" = "شبیه سازی"
|
||||||
"cloneInbound" = "ایجاد"
|
"cloneInbound" = "ایجاد"
|
||||||
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
"cloneInboundContent" = "همه موارد این ورودی بجز پورت ، ای پی و کلاینت ها شبیه سازی خواهند شد"
|
||||||
"cloneInboundOk" = "ساختن شبیه ساز"
|
"cloneInboundOk" = "ساختن شبیه ساز"
|
||||||
@@ -158,8 +165,10 @@
|
|||||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"XTLSdec" = "هسته Xray باید 1.7.5 باشد"
|
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
||||||
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||||
|
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot)"
|
||||||
|
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "کاربر جدید"
|
"add" = "کاربر جدید"
|
||||||
@@ -197,18 +206,17 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "رمزنگاری"
|
"encryption" = "رمزنگاری"
|
||||||
|
|
||||||
[pages.setting]
|
[pages.settings]
|
||||||
"title" = "تنظیمات"
|
"title" = "تنظیمات"
|
||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
"actions" = "عملیات ها"
|
"actions" = "عملیات ها"
|
||||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelSettings" = "تنظیمات پنل"
|
||||||
"userSetting" = "تنظیمات مدیر"
|
"securitySettings" = "تنظیمات امنیتی"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
"TGReminder" = "تنظیمات ربات تلگرام"
|
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
||||||
"otherSetting" = "دیگر تنظیمات"
|
|
||||||
"panelListeningIP" = "محدودیت آی پی پنل"
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
@@ -223,9 +231,32 @@
|
|||||||
"currentPassword" = "رمز عبور فعلی"
|
"currentPassword" = "رمز عبور فعلی"
|
||||||
"newUsername" = "نام کاربری جدید"
|
"newUsername" = "نام کاربری جدید"
|
||||||
"newPassword" = "رمز عبور جدید"
|
"newPassword" = "رمز عبور جدید"
|
||||||
"basicTemplate" = "بخش پایه"
|
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
||||||
"advancedTemplate" = "بخش های پیشرفته الگو"
|
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
"telegramToken" = "توکن تلگرام"
|
||||||
|
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
|
"telegramChatIdDesc" = "از @userinfobot برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
|
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||||
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
|
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||||
|
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||||
|
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
||||||
|
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
||||||
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
|
"timeZone" = "منظقه زمانی"
|
||||||
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
|
||||||
|
[pages.settings.templates]
|
||||||
|
"title" = "الگوها"
|
||||||
|
"basicTemplate" = "بخش الگو پایه"
|
||||||
|
"advancedTemplate" = "بخش الگو پیشرفته"
|
||||||
|
"completeTemplate" = "بخش الگو کامل"
|
||||||
"generalConfigs" = "تنظیمات عمومی"
|
"generalConfigs" = "تنظیمات عمومی"
|
||||||
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
||||||
"countryConfigs" = "تنظیمات برای کشورها"
|
"countryConfigs" = "تنظیمات برای کشورها"
|
||||||
@@ -276,34 +307,18 @@
|
|||||||
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
||||||
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
|
||||||
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
[pages.settings.security]
|
||||||
"telegramToken" = "توکن تلگرام"
|
"admin" = "مدیر"
|
||||||
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"secret" = "توکن امنیتی"
|
||||||
"telegramChatId" = "آی دی تلگرام مدیریت"
|
|
||||||
"telegramChatIdDesc" = "با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
|
||||||
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
|
||||||
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
|
||||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
|
||||||
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
|
||||||
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
|
||||||
"trafficDiffDesc" = "فاصله زمانی هشدار تا رسیدن به اتمام ترافیک (واحد: گیگابایت)"
|
|
||||||
"tgNotifyCpu" = "آستانه هشدار درصد پردازنده"
|
|
||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
|
||||||
"timeZonee" = "منظقه زمانی"
|
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
|
||||||
"loginSecurity" = "لاگین ایمن"
|
"loginSecurity" = "لاگین ایمن"
|
||||||
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
|
"loginSecurityDesc" = "افزودن یک مرحله دیگر به فرآیند لاگین"
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
|
"secretTokenDesc" = "این کد امنیتی را نزد خود در این جای امن نگه داری، بدون این کد امکان ورود به پنل را نخواهید داشت. امکان بازیابی آن وجود ندارد!"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySetting" = "ویرایش تنظیمات"
|
"modifySettings" = "ویرایش تنظیمات"
|
||||||
"getSetting" = "دریافت تنظیمات"
|
"getSettings" = "دریافت تنظیمات"
|
||||||
"modifyUser" = "ویرایش کاربر"
|
"modifyUser" = "ویرایش کاربر"
|
||||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
||||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
"inbounds" = "入站列表"
|
"inbounds" = "入站列表"
|
||||||
"setting" = "面板设置"
|
"settings" = "面板设置"
|
||||||
"logout" = "退出登录"
|
"logout" = "退出登录"
|
||||||
"link" = "其他"
|
"link" = "其他"
|
||||||
|
|
||||||
@@ -89,7 +89,14 @@
|
|||||||
"totalReceive" = "系统启动以来所有网卡的总下载流量"
|
"totalReceive" = "系统启动以来所有网卡的总下载流量"
|
||||||
"xraySwitchVersionDialog" = "切换 xray 版本"
|
"xraySwitchVersionDialog" = "切换 xray 版本"
|
||||||
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至"
|
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至"
|
||||||
"dontRefreshh" = "安装中,请不要刷新此页面"
|
"dontRefresh" = "安装中,请不要刷新此页面"
|
||||||
|
"logs" = "日志"
|
||||||
|
"config" = "配置"
|
||||||
|
"backup" = "备份还原"
|
||||||
|
"backupTitle" = "备份和恢复数据库"
|
||||||
|
"backupDescription" = "请记住在导入新数据库之前进行备份。"
|
||||||
|
"exportDatabase" = "下载数据库"
|
||||||
|
"importDatabase" = "上传数据库"
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "入站列表"
|
"title" = "入站列表"
|
||||||
@@ -108,8 +115,8 @@
|
|||||||
"resetTraffic" = "重置流量"
|
"resetTraffic" = "重置流量"
|
||||||
"addInbound" = "添加入"
|
"addInbound" = "添加入"
|
||||||
"generalActions" = "通用操作"
|
"generalActions" = "通用操作"
|
||||||
"addTo" = "添加"
|
"create" = "添加"
|
||||||
"revise" = "修改"
|
"update" = "修改"
|
||||||
"modifyInbound" = "修改入站"
|
"modifyInbound" = "修改入站"
|
||||||
"deleteInbound" = "删除入站"
|
"deleteInbound" = "删除入站"
|
||||||
"deleteInboundContent" = "确定要删除入站吗?"
|
"deleteInboundContent" = "确定要删除入站吗?"
|
||||||
@@ -134,7 +141,7 @@
|
|||||||
"clickOnQRcode" = "点击二维码复制"
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
"client" = "客户"
|
"client" = "客户"
|
||||||
"export" = "导出链接"
|
"export" = "导出链接"
|
||||||
"Clone" = "克隆"
|
"clone" = "克隆"
|
||||||
"cloneInbound" = "创造"
|
"cloneInbound" = "创造"
|
||||||
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
|
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆"
|
||||||
"cloneInboundOk" = "从创建克隆"
|
"cloneInboundOk" = "从创建克隆"
|
||||||
@@ -158,8 +165,10 @@
|
|||||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||||
"IPLimitlogclear" = "清除日志"
|
"IPLimitlogclear" = "清除日志"
|
||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
"XTLSdec" = "Xray核心需要1.7.5"
|
"xtlsDesc" = "Xray核心需要1.7.5"
|
||||||
"Realitydec" = "Xray核心需要1.8.0及以上版本"
|
"realityDesc" = "Xray核心需要1.8.0及以上版本"
|
||||||
|
"telegramDesc" = "使用不带@的电报 ID 或聊天 ID(您可以在此处获取 @userinfobot)"
|
||||||
|
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
"add" = "添加客户端"
|
"add" = "添加客户端"
|
||||||
@@ -197,18 +206,17 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "加密"
|
"encryption" = "加密"
|
||||||
|
|
||||||
[pages.setting]
|
[pages.settings]
|
||||||
"title" = "设置"
|
"title" = "设置"
|
||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
"restartPanel" = "重启面板"
|
"restartPanel" = "重启面板"
|
||||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||||
"actions" = "动作"
|
"actions" = "动作"
|
||||||
"resetDefaultConfig" = "重置为默认配置"
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelConfig" = "面板配置"
|
"panelSettings" = "面板配置"
|
||||||
"userSetting" = "用户设置"
|
"securitySettings" = "安全设定"
|
||||||
"xrayConfiguration" = "xray 相关设置"
|
"xrayConfiguration" = "xray 相关设置"
|
||||||
"TGReminder" = "TG提醒相关设置"
|
"TGBotSettings" = "TG提醒相关设置"
|
||||||
"otherSetting" = "其他设置"
|
|
||||||
"panelListeningIP" = "面板监听 IP"
|
"panelListeningIP" = "面板监听 IP"
|
||||||
"panelListeningIPDesc" = "默认留空监听所有 IP,重启面板生效"
|
"panelListeningIPDesc" = "默认留空监听所有 IP,重启面板生效"
|
||||||
"panelPort" = "面板监听端口"
|
"panelPort" = "面板监听端口"
|
||||||
@@ -223,6 +231,29 @@
|
|||||||
"currentPassword" = "原密码"
|
"currentPassword" = "原密码"
|
||||||
"newUsername" = "新用户名"
|
"newUsername" = "新用户名"
|
||||||
"newPassword" = "新密码"
|
"newPassword" = "新密码"
|
||||||
|
"telegramBotEnable" = "启用电报机器人"
|
||||||
|
"telegramBotEnableDesc" = "重启面板生效"
|
||||||
|
"telegramToken" = "电报机器人TOKEN"
|
||||||
|
"telegramTokenDesc" = "重启面板生效"
|
||||||
|
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
||||||
|
"telegramChatIdDesc" = "多个聊天 ID 以逗号分隔。使用@userinfobot 获取您的聊天 ID。重新启动面板以应用更改。"
|
||||||
|
"telegramNotifyTime" = "电报机器人通知时间"
|
||||||
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
|
"tgNotifyBackup" = "数据库备份"
|
||||||
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||||
|
"sessionMaxAge" = "会话最大年龄"
|
||||||
|
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
||||||
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
|
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||||
|
"trafficDiff" = "耗尽流量阈值"
|
||||||
|
"trafficDiffDesc" = "完成流量前检测耗尽(单位:GB)"
|
||||||
|
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
||||||
|
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
||||||
|
"timeZone" = "时区"
|
||||||
|
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
||||||
|
|
||||||
|
[pages.settings.templates]
|
||||||
|
"title" = "模板"
|
||||||
"basicTemplate" = "基本模板"
|
"basicTemplate" = "基本模板"
|
||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"completeTemplate" = "Xray 配置的完整模板"
|
"completeTemplate" = "Xray 配置的完整模板"
|
||||||
@@ -276,34 +307,18 @@
|
|||||||
"xrayConfigOutboundsDesc" = "更改配置模板定义此服务器的传出方式,重启面板生效"
|
"xrayConfigOutboundsDesc" = "更改配置模板定义此服务器的传出方式,重启面板生效"
|
||||||
"xrayConfigRoutings" = "路由规则配置"
|
"xrayConfigRoutings" = "路由规则配置"
|
||||||
"xrayConfigRoutingsDesc" = "更改配置模板为该服务器定义路由规则,重启面板生效"
|
"xrayConfigRoutingsDesc" = "更改配置模板为该服务器定义路由规则,重启面板生效"
|
||||||
"telegramBotEnable" = "启用电报机器人"
|
|
||||||
"telegramBotEnableDesc" = "重启面板生效"
|
[pages.settings.security]
|
||||||
"telegramToken" = "电报机器人TOKEN"
|
"admin" = "行政"
|
||||||
"telegramTokenDesc" = "重启面板生效"
|
"secret" = "秘密令牌"
|
||||||
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效"
|
|
||||||
"telegramChatIdDesc" = "重启面板生效"
|
|
||||||
"telegramNotifyTime" = "电报机器人通知时间"
|
|
||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
|
||||||
"tgNotifyBackup" = "数据库备份"
|
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
|
||||||
"sessionMaxAge" = "会话最大年龄"
|
|
||||||
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
|
||||||
"expireTimeDiff" = "耗尽时间阈值"
|
|
||||||
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
|
||||||
"trafficDiff" = "耗尽流量阈值"
|
|
||||||
"trafficDiffDesc" = "完成流量前检测耗尽(单位:GB)"
|
|
||||||
"tgNotifyCpu" = "CPU 百分比警报阈值"
|
|
||||||
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知"
|
|
||||||
"timeZonee" = "时区"
|
|
||||||
"timeZoneDesc" = "定时任务按照该时区的时间运行,重启面板生效"
|
|
||||||
"loginSecurity" = "登录安全"
|
"loginSecurity" = "登录安全"
|
||||||
"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
|
"loginSecurityDesc" = "在用户登录页面中切换附加步骤"
|
||||||
"secretToken" = "秘密令牌"
|
"secretToken" = "秘密令牌"
|
||||||
"secretTokenDesc" = "复制此秘密令牌并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复"
|
"secretTokenDesc" = "复制此秘密令牌并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySetting" = "修改设置"
|
"modifySettings" = "修改设置"
|
||||||
"getSetting" = "获取设置"
|
"getSettings" = "获取设置"
|
||||||
"modifyUser" = "修改用户"
|
"modifyUser" = "修改用户"
|
||||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||||
|
|||||||
Reference in New Issue
Block a user