Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63939244a4 | ||
|
|
213b693bd3 | ||
|
|
a289ef5d10 | ||
|
|
955eb8f142 | ||
|
|
d396fb5d06 | ||
|
|
b5dd258074 | ||
|
|
c855a292cb | ||
|
|
f2132c62e9 | ||
|
|
94a3807353 | ||
|
|
7cacfc074e | ||
|
|
9e8ac8a087 | ||
|
|
e64a9eeee6 | ||
|
|
a55a1a7102 | ||
|
|
46bc39c160 | ||
|
|
2a182d8b9a | ||
|
|
77241c7fcf | ||
|
|
fd6a85afd9 | ||
|
|
9a89d7bfab | ||
|
|
edd6b22109 | ||
|
|
5468069bef | ||
|
|
0cce35784e | ||
|
|
80c1e58ed5 | ||
|
|
b0871a6ef6 | ||
|
|
288374d5fa | ||
|
|
1f7c79c735 | ||
|
|
251fd608df | ||
|
|
456941323b | ||
|
|
a6a77688dc | ||
|
|
09cd2248dc | ||
|
|
8143379645 | ||
|
|
5bd6baa055 | ||
|
|
41e9290574 | ||
|
|
cf7d50617b | ||
|
|
95e006963c | ||
|
|
65588a4492 | ||
|
|
d39c7e4ae3 | ||
|
|
3bec9ee273 | ||
|
|
7b3628d33b | ||
|
|
ad1aa5b2f9 | ||
|
|
46ef8c503e | ||
|
|
721fec3b5a | ||
|
|
30a5f66f26 | ||
|
|
bb6e6861ca | ||
|
|
4c0e391597 | ||
|
|
43c1fc9aad | ||
|
|
7a48cbb191 | ||
|
|
004d69392b | ||
|
|
fc0882805d | ||
|
|
f553922d53 | ||
|
|
7b2764566c | ||
|
|
55d38dfa48 | ||
|
|
0e266b88f0 | ||
|
|
7bb3e517b2 | ||
|
|
7d0c3b6517 | ||
|
|
67201fc678 | ||
|
|
d137deccfa | ||
|
|
00777e3a25 | ||
|
|
bcb2f125ff | ||
|
|
37ab8f42e9 | ||
|
|
cf1cfbee96 | ||
|
|
d89e03023f | ||
|
|
5ec1559c7b | ||
|
|
cf2b1fd9ec | ||
|
|
ddbc1602ce | ||
|
|
77cee098ad | ||
|
|
89b94c2c90 | ||
|
|
350743fea3 | ||
|
|
8011d0b6c6 | ||
|
|
9e5d7ac1d0 | ||
|
|
e01fb9b605 | ||
|
|
52a468d586 | ||
|
|
c73c71cc83 | ||
|
|
2141d62069 | ||
|
|
f286c9a86a | ||
|
|
b4fd254c71 | ||
|
|
0a8cf7e41b | ||
|
|
abd48551fd | ||
|
|
bb9a10051f | ||
|
|
5c6406ab58 | ||
|
|
a25137f215 | ||
|
|
50c296eb28 | ||
|
|
b1a302de95 | ||
|
|
7b567458ff | ||
|
|
13e3e23f20 | ||
|
|
efecdf5fd0 | ||
|
|
f21904caf2 | ||
|
|
15a97af215 | ||
|
|
20a55c086e | ||
|
|
c727b81772 | ||
|
|
77692e0298 | ||
|
|
5e3e965647 | ||
|
|
88cde18bb2 |
14
.github/workflows/docker.yml
vendored
@@ -11,15 +11,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -27,15 +27,15 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.4.0
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64, linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
22
DockerInit.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
if [ $1 == "amd64" ]; then
|
||||
ARCH="64";
|
||||
FNAME="amd64";
|
||||
elif [ $1 == "arm64" ]; then
|
||||
ARCH="arm64-v8a"
|
||||
FNAME="arm64";
|
||||
else
|
||||
ARCH="64";
|
||||
FNAME="amd64";
|
||||
fi
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||
|
||||
cd ../../
|
||||
60
Dockerfile
@@ -1,54 +1,20 @@
|
||||
# Use the official Golang image as the base image
|
||||
FROM golang:1.20 as builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETOS
|
||||
|
||||
# Set up the working directory
|
||||
#Build latest x-ui from source
|
||||
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the Go modules and download the dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source code
|
||||
ARG TARGETARCH
|
||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||
COPY . .
|
||||
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
|
||||
RUN ./DockerInit.sh "$TARGETARCH"
|
||||
|
||||
# Build 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
|
||||
#Build app image using latest x-ui
|
||||
FROM alpine
|
||||
ENV TZ=Asia/Tehran
|
||||
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
|
||||
RUN apk add ca-certificates tzdata
|
||||
|
||||
# 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"]
|
||||
COPY --from=builder /app/build/ /app/
|
||||
VOLUME [ "/etc/x-ui" ]
|
||||
ENTRYPOINT [ "/app/x-ui" ]
|
||||
|
||||
53
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)
|
||||
@@ -7,12 +8,13 @@
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
||||
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
|
||||
**Buy Me a Coffee :**
|
||||
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
|
||||
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
|
||||
# Install & Upgrade
|
||||
|
||||
@@ -22,10 +24,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||
|
||||
## Install custom version
|
||||
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.4.0`:
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.4.5`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.4.0
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.4.5
|
||||
```
|
||||
|
||||
# SSL
|
||||
@@ -47,12 +49,12 @@ or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||
|
||||
Before you set ssl on settings
|
||||
|
||||
- http://ip:2053/xui
|
||||
- http://domain:2053/xui
|
||||
- http://ip:2053/panel
|
||||
- http://domain:2053/panel
|
||||
|
||||
After you set ssl on settings
|
||||
|
||||
- https://yourdomain:2053/xui
|
||||
- https://yourdomain:2053/panel
|
||||
|
||||
# Environment Variables
|
||||
|
||||
@@ -69,6 +71,31 @@ Example:
|
||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||
```
|
||||
|
||||
# Install with Docker
|
||||
|
||||
1. Install Docker:
|
||||
```sh
|
||||
bash <(curl -sSL https://get.docker.com)
|
||||
```
|
||||
2. Run 3x-ui:
|
||||
|
||||
```sh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```sh
|
||||
docker run -itd \
|
||||
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||
-v $PWD/db/:/etc/x-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name 3x-ui \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
|
||||
# Xray Configurations:
|
||||
|
||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||
@@ -173,19 +200,19 @@ Reference syntax:
|
||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* |
|
||||
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* |
|
||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
|
||||
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
|
||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||
|
||||
*- The field `clientId` should be filled by:
|
||||
\*- 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)
|
||||
|
||||
# A Special Thanks To
|
||||
@@ -205,6 +232,8 @@ Reference syntax:
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Stargazers over time
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.4.0
|
||||
1.4.5
|
||||
16
docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
3x-ui:
|
||||
image: ghcr.io/mhsanaei/3x-ui:latest
|
||||
container_name: 3x-ui
|
||||
hostname: yourhostname
|
||||
volumes:
|
||||
- $PWD/db/:/etc/x-ui/
|
||||
- $PWD/cert/:/root/cert/
|
||||
environment:
|
||||
XRAY_VMESS_AEAD_FORCED: "false"
|
||||
tty: true
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
2
go.mod
@@ -19,7 +19,7 @@ require (
|
||||
golang.org/x/text v0.9.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
gorm.io/driver/sqlite v1.5.0
|
||||
gorm.io/gorm v1.25.0
|
||||
gorm.io/gorm v1.25.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
4
go.sum
@@ -252,8 +252,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
22
install.sh
@@ -25,9 +25,9 @@ echo "The OS release is: $release"
|
||||
|
||||
arch3xui() {
|
||||
case "$(uname -m)" in
|
||||
x86_64 | x64 | amd64 ) echo 'amd64' ;;
|
||||
armv8 | arm64 | aarch64 ) echo 'arm64' ;;
|
||||
* ) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||
x86_64 | x64 | amd64) echo 'amd64' ;;
|
||||
armv8 | arm64 | aarch64) echo 'arm64' ;;
|
||||
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||
esac
|
||||
}
|
||||
echo "arch: $(arch3xui)"
|
||||
@@ -39,7 +39,7 @@ if [[ "${release}" == "centos" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 20 ]]; then
|
||||
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||
fi
|
||||
@@ -59,18 +59,17 @@ fi
|
||||
|
||||
install_base() {
|
||||
case "${release}" in
|
||||
centos|fedora)
|
||||
yum install -y -q wget curl tar
|
||||
;;
|
||||
*)
|
||||
apt install -y -q wget curl tar
|
||||
;;
|
||||
centos | fedora)
|
||||
yum install -y -q wget curl tar
|
||||
;;
|
||||
*)
|
||||
apt install -y -q wget curl tar
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#This function will be called when user installed x-ui out of sercurity
|
||||
config_after_install() {
|
||||
/usr/local/x-ui/x-ui migrate
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
@@ -101,6 +100,7 @@ config_after_install() {
|
||||
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
||||
fi
|
||||
fi
|
||||
/usr/local/x-ui/x-ui migrate
|
||||
}
|
||||
|
||||
install_x-ui() {
|
||||
|
||||
BIN
media/1.png
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 58 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 37 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 99 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 23 KiB |
BIN
media/5.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
media/6.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
@@ -6,8 +6,6 @@ import (
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
var CtxDone = errors.New("context done")
|
||||
|
||||
func NewErrorf(format string, a ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
return errors.New(msg)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package common
|
||||
|
||||
import "sort"
|
||||
|
||||
func IsSubString(target string, str_array []string) bool {
|
||||
sort.Strings(str_array)
|
||||
index := sort.SearchStrings(str_array, target)
|
||||
return index < len(str_array) && str_array[index] == target
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package util
|
||||
|
||||
import "context"
|
||||
|
||||
func IsDone(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON 自定义 json.RawMessage 默认行为
|
||||
// MarshalJSON: Customize json.RawMessage default behavior
|
||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
if len(m) == 0 {
|
||||
return []byte("null"), nil
|
||||
@@ -14,7 +14,7 @@ func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
// UnmarshalJSON: sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||
|
||||
143
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
@@ -31,9 +31,9 @@ small{font-size:80%}
|
||||
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}
|
||||
sub{bottom:-.25em}
|
||||
sup{top:-.5em}
|
||||
a{color:#1890ff;text-decoration:none;background-color:transparent;outline:0;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}
|
||||
a:hover{color:#40a9ff}
|
||||
a:active{color:#096dd9}
|
||||
a{color:rgb(0 150 112);text-decoration:none;background-color:transparent;outline:0;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}
|
||||
a:hover{color:rgb(0 150 112)}
|
||||
a:active{color:rgb(10, 105, 82)}
|
||||
a:active,a:hover{text-decoration:none;outline:0}
|
||||
a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}
|
||||
code,kbd,pre,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}
|
||||
@@ -520,8 +520,8 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
||||
.ant-select-arrow .ant-select-arrow-icon{display:block}
|
||||
.ant-select-arrow .ant-select-arrow-icon svg{transition:transform .3s}
|
||||
.ant-select-selection{display:block;box-sizing:border-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-radius:4px;outline:0;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-select-selection:hover{border-color:#40a9ff;border-right-width:1px!important}
|
||||
.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-select-selection:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
||||
.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:rgb(0, 150, 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||
.ant-select-selection__clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;font-style:normal;line-height:12px;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}
|
||||
.ant-select-selection__clear:before{display:block}
|
||||
.ant-select-selection__clear:hover{color:rgba(0,0,0,.45)}
|
||||
@@ -529,7 +529,7 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
||||
.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
||||
.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}
|
||||
.ant-select-disabled{color:rgba(0,0,0,.25)}
|
||||
.ant-select-disabled .ant-select-selection{background:#f5f5f5;cursor:not-allowed}
|
||||
.ant-select-disabled .ant-select-selection{background:rgb(0 150 112);cursor:not-allowed}
|
||||
.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}
|
||||
.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}
|
||||
.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}
|
||||
@@ -582,7 +582,7 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
||||
.ant-select-selection--multiple .ant-select-arrow,.ant-select-selection--multiple .ant-select-selection__clear{top:16px}
|
||||
.ant-select-allow-clear .ant-select-selection--multiple .ant-select-selection__rendered,.ant-select-show-arrow .ant-select-selection--multiple .ant-select-selection__rendered{margin-right:20px}
|
||||
.ant-select-open .ant-select-arrow-icon svg{transform:rotate(180deg)}
|
||||
.ant-select-open .ant-select-selection{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-select-open .ant-select-selection{border-color:rgb(0 150 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||
.ant-select-combobox .ant-select-arrow{display:none}
|
||||
.ant-select-combobox .ant-select-search--inline{float:none;width:100%;height:100%}
|
||||
.ant-select-combobox .ant-select-search__field__wrap{width:100%;height:100%}
|
||||
@@ -629,8 +629,8 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
||||
.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}
|
||||
.ant-input:-ms-input-placeholder{text-overflow:ellipsis}
|
||||
.ant-input:placeholder-shown{text-overflow:ellipsis}
|
||||
.ant-input:focus,.ant-input:hover{border-color:#40a9ff;border-right-width:1px!important}
|
||||
.ant-input:focus{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-input:focus,.ant-input:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important;}
|
||||
.ant-input:focus{outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||
.ant-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||
.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||
.ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||
@@ -716,7 +716,7 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
||||
.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
||||
.ant-btn>a:only-child{color:currentColor}
|
||||
.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn:focus,.ant-btn:hover{color:#40a9ff;background-color:#fff;border-color:#40a9ff}
|
||||
.ant-btn:focus,.ant-btn:hover{color:rgb(0 150 112);background-color:#fff;border-color:rgb(0 150 112)}
|
||||
.ant-btn:focus>a:only-child,.ant-btn:hover>a:only-child{color:currentColor}
|
||||
.ant-btn:focus>a:only-child:after,.ant-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn.active,.ant-btn:active{color:#096dd9;background-color:#fff;border-color:#096dd9}
|
||||
@@ -727,16 +727,16 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
||||
.ant-btn-disabled.active>a:only-child:after,.ant-btn-disabled:active>a:only-child:after,.ant-btn-disabled:focus>a:only-child:after,.ant-btn-disabled:hover>a:only-child:after,.ant-btn-disabled>a:only-child:after,.ant-btn.disabled.active>a:only-child:after,.ant-btn.disabled:active>a:only-child:after,.ant-btn.disabled:focus>a:only-child:after,.ant-btn.disabled:hover>a:only-child:after,.ant-btn.disabled>a:only-child:after,.ant-btn[disabled].active>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn.active,.ant-btn:active,.ant-btn:focus,.ant-btn:hover{text-decoration:none;background:#fff}
|
||||
.ant-btn>i,.ant-btn>span{display:inline-block;transition:margin-left .3s cubic-bezier(.645,.045,.355,1);pointer-events:none}
|
||||
.ant-btn-primary{color:#fff;background-color:#1890ff;border-color:#1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||
.ant-btn-primary{color:#fff;background-color:rgb(0, 150, 112);border-color:rgb(0, 150, 112);text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||
.ant-btn-primary>a:only-child{color:currentColor}
|
||||
.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:#40a9ff;border-color:#00000017}
|
||||
.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:rgb(0, 185, 138);border-color:#00000017}
|
||||
.ant-btn-primary:focus>a:only-child,.ant-btn-primary:hover>a:only-child{color:currentColor}
|
||||
.ant-btn-primary:focus>a:only-child:after,.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:#096dd9;border-color:#096dd9}
|
||||
.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:rgb(25, 191, 149);border-color:rgb(25, 191, 149)}
|
||||
.ant-btn-primary.active>a:only-child,.ant-btn-primary:active>a:only-child{color:currentColor}
|
||||
.ant-btn-primary.active>a:only-child:after,.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
||||
.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||
.ant-btn-primary-disabled.active>a:only-child,.ant-btn-primary-disabled:active>a:only-child,.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-primary-disabled>a:only-child,.ant-btn-primary.disabled.active>a:only-child,.ant-btn-primary.disabled:active>a:only-child,.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-primary.disabled>a:only-child,.ant-btn-primary[disabled].active>a:only-child,.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]>a:only-child{color:currentColor}
|
||||
.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-primary-disabled>a:only-child:after,.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-primary.disabled>a:only-child:after,.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#40a9ff;border-left-color:#40a9ff}
|
||||
@@ -769,16 +769,16 @@ textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;ve
|
||||
.ant-btn-dashed-disabled,.ant-btn-dashed-disabled.active,.ant-btn-dashed-disabled:active,.ant-btn-dashed-disabled:focus,.ant-btn-dashed-disabled:hover,.ant-btn-dashed.disabled,.ant-btn-dashed.disabled.active,.ant-btn-dashed.disabled:active,.ant-btn-dashed.disabled:focus,.ant-btn-dashed.disabled:hover,.ant-btn-dashed[disabled],.ant-btn-dashed[disabled].active,.ant-btn-dashed[disabled]:active,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
||||
.ant-btn-dashed-disabled.active>a:only-child,.ant-btn-dashed-disabled:active>a:only-child,.ant-btn-dashed-disabled:focus>a:only-child,.ant-btn-dashed-disabled:hover>a:only-child,.ant-btn-dashed-disabled>a:only-child,.ant-btn-dashed.disabled.active>a:only-child,.ant-btn-dashed.disabled:active>a:only-child,.ant-btn-dashed.disabled:focus>a:only-child,.ant-btn-dashed.disabled:hover>a:only-child,.ant-btn-dashed.disabled>a:only-child,.ant-btn-dashed[disabled].active>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]>a:only-child{color:currentColor}
|
||||
.ant-btn-dashed-disabled.active>a:only-child:after,.ant-btn-dashed-disabled:active>a:only-child:after,.ant-btn-dashed-disabled:focus>a:only-child:after,.ant-btn-dashed-disabled:hover>a:only-child:after,.ant-btn-dashed-disabled>a:only-child:after,.ant-btn-dashed.disabled.active>a:only-child:after,.ant-btn-dashed.disabled:active>a:only-child:after,.ant-btn-dashed.disabled:focus>a:only-child:after,.ant-btn-dashed.disabled:hover>a:only-child:after,.ant-btn-dashed.disabled>a:only-child:after,.ant-btn-dashed[disabled].active>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-danger{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||
.ant-btn-danger{color:#ff4d4f;background-color:rgb(255 77 79 / 0%);border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}
|
||||
.ant-btn-danger>a:only-child{color:currentColor}
|
||||
.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff7875;border-color:#ff7875}
|
||||
.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f}
|
||||
.ant-btn-danger:focus>a:only-child,.ant-btn-danger:hover>a:only-child{color:currentColor}
|
||||
.ant-btn-danger:focus>a:only-child:after,.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-danger.active,.ant-btn-danger:active{color:#fff;background-color:#d9363e;border-color:#d9363e}
|
||||
.ant-btn-danger.active>a:only-child,.ant-btn-danger:active>a:only-child{color:currentColor}
|
||||
.ant-btn-danger.active>a:only-child:after,.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
||||
.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||
.ant-btn-danger-disabled.active>a:only-child,.ant-btn-danger-disabled:active>a:only-child,.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-danger-disabled>a:only-child,.ant-btn-danger.disabled.active>a:only-child,.ant-btn-danger.disabled:active>a:only-child,.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-danger.disabled>a:only-child,.ant-btn-danger[disabled].active>a:only-child,.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]>a:only-child{color:currentColor}
|
||||
.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-danger-disabled>a:only-child:after,.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-danger.disabled>a:only-child:after,.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-btn-link{color:#1890ff;background-color:transparent;border-color:transparent;box-shadow:none}
|
||||
@@ -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:hover{color:#1890ff}
|
||||
.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:#1890ff;background-color:#ebebeb}
|
||||
.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#2d2d2d;background-image: linear-gradient(90deg,#99999980 0,#8888889e 100%);border-radius: 0.5rem}
|
||||
.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}
|
||||
.ant-menu-horizontal>.ant-menu-item-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:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#e6f7ff}
|
||||
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background: linear-gradient(90deg,#009670 0,#026247 100%);color: #fff;border-radius: 0.5rem}
|
||||
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
||||
.ant-menu-vertical-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}
|
||||
@@ -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: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: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-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}
|
||||
@@ -1080,9 +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-selected{color:#fff;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:#206bd3}
|
||||
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#ffffff}
|
||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#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-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#38383800}
|
||||
.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}
|
||||
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-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}
|
||||
@@ -1236,18 +1236,18 @@ span.ant-radio+*{padding-right:8px;padding-left:8px}
|
||||
.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:4px 0 0 4px}
|
||||
.ant-radio-button-wrapper:last-child{border-radius:0 4px 4px 0}
|
||||
.ant-radio-button-wrapper:first-child:last-child{border-radius:4px}
|
||||
.ant-radio-button-wrapper:hover{position:relative;color:#1890ff}
|
||||
.ant-radio-button-wrapper:hover{position:relative;color:#009670}
|
||||
.ant-radio-button-wrapper:focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||
.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#1890ff;background:#fff;border-color:#1890ff;box-shadow:-1px 0 0 0 #1890ff}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#1890ff!important;opacity:.1}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#1890ff;box-shadow:none!important}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#40a9ff;border-color:#40a9ff;box-shadow:-1px 0 0 0 #40a9ff}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#096dd9;border-color:#096dd9;box-shadow:-1px 0 0 0 #096dd9}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#009670;background:#fff;border-color:#009670;box-shadow:-1px 0 0 0 #009670}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#009670!important;opacity:.1}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#009670;box-shadow:none!important}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#009670;border-color:#009670;box-shadow:-1px 0 0 0 #009670}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#076e54;border-color:#076e54;box-shadow:-1px 0 0 0 #076e54}
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#1890ff;border-color:#1890ff}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#40a9ff;border-color:#40a9ff}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#096dd9;border-color:#096dd9}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#009670;border-color:#009670}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#009670;border-color:#009670}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#076e54;border-color:#076e54}
|
||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{outline:3px solid rgba(24,144,255,.06)}
|
||||
.ant-radio-button-wrapper-disabled{cursor:not-allowed}
|
||||
.ant-radio-button-wrapper-disabled,.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9}
|
||||
@@ -1267,8 +1267,8 @@ to{transform:scale(1.6);opacity:0}
|
||||
.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: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 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-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:2px solid rgba(155, 155, 155, 0.15);border-radius:2px 2px 0 0;zoom:1}
|
||||
.ant-card-head:after,.ant-card-head:before{display:table;content:""}
|
||||
.ant-card-head:after{clear:both}
|
||||
.ant-card-head-wrapper{display:flex;align-items:center}
|
||||
@@ -1357,11 +1357,11 @@ 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 .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{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{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1;border-radius:1.5rem;/*box-shadow:0 1px 7px -1px #0000005c;*/transition:all .3s;background-color: white}
|
||||
.ant-tabs:hover{box-shadow:0 3px 12px -.8px #0000005c}
|
||||
.ant-tabs:after,.ant-tabs:before{display:table;content:""}
|
||||
.ant-tabs:after{clear:both}
|
||||
.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;box-sizing:border-box;width:0;height:2px;background-color:#1890ff;transform-origin:0 0}
|
||||
.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;box-sizing:border-box;width:0;height:2px;background-color:rgb(0 150 112);transform-origin:0 0}
|
||||
.ant-tabs-bar{margin: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-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}
|
||||
@@ -1390,10 +1390,10 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-tabs-nav .ant-tabs-tab{position:relative;display:inline-block;box-sizing:border-box;height:100%;margin:0 32px 0 0;padding:12px 16px;text-decoration:none;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1)}
|
||||
.ant-tabs-nav .ant-tabs-tab:before{position:absolute;top:-1px;left:0;width:100%;border-top:2px solid transparent;border-radius:4px 4px 0 0;transition:all .3s;content:"";pointer-events:none}
|
||||
.ant-tabs-nav .ant-tabs-tab:last-child{margin-right:0}
|
||||
.ant-tabs-nav .ant-tabs-tab:hover{color:#40a9ff}
|
||||
.ant-tabs-nav .ant-tabs-tab:active{color:#096dd9}
|
||||
.ant-tabs-nav .ant-tabs-tab:hover{color:rgb(0 150 112)}
|
||||
.ant-tabs-nav .ant-tabs-tab:active{color:rgb(0 150 112)}
|
||||
.ant-tabs-nav .ant-tabs-tab .anticon{margin-right:8px}
|
||||
.ant-tabs-nav .ant-tabs-tab-active{color:#1890ff;font-weight:500}
|
||||
.ant-tabs-nav .ant-tabs-tab-active{color:rgb(0 150 112);font-weight:500}
|
||||
.ant-tabs-nav .ant-tabs-tab-disabled,.ant-tabs-nav .ant-tabs-tab-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}
|
||||
.ant-tabs .ant-tabs-large-bar .ant-tabs-nav-container{font-size:16px}
|
||||
.ant-tabs .ant-tabs-large-bar .ant-tabs-tab{padding:16px}
|
||||
@@ -2438,14 +2438,14 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:rgba(0,0,0,.25)}
|
||||
.ant-cascader-menu-item .ant-cascader-menu-item-keyword{color:#f5222d}
|
||||
.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:0;cursor:pointer}
|
||||
.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:#1890ff}
|
||||
.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:""}
|
||||
.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:rgb(0, 150, 112)}
|
||||
.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid rgb(0, 150, 112);border-radius:2px;visibility:hidden;-webkit-animation:antCheckboxEffect .36s ease-in-out;animation:antCheckboxEffect .36s ease-in-out;-webkit-animation-fill-mode:backwards;animation-fill-mode:backwards;content:""}
|
||||
.ant-checkbox-wrapper:hover .ant-checkbox:after,.ant-checkbox:hover:after{visibility:visible}
|
||||
.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}
|
||||
.ant-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}
|
||||
.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}
|
||||
.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}
|
||||
.ant-checkbox-checked .ant-checkbox-inner{background-color:#1890ff;border-color:#1890ff}
|
||||
.ant-checkbox-checked .ant-checkbox-inner{background-color:rgb(0, 150, 112);border-color:rgb(0, 150, 112)}
|
||||
.ant-checkbox-disabled{cursor:not-allowed}
|
||||
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:rgba(0,0,0,.25);-webkit-animation-name:none;animation-name:none}
|
||||
.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed}
|
||||
@@ -2464,9 +2464,9 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}
|
||||
.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#1890ff;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}
|
||||
.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}
|
||||
.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:4px}
|
||||
.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}
|
||||
.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 4px 4px}
|
||||
.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"/*;background-color:#fafafa*/;border:1px solid rgba(100, 100, 100, 0.2);/*border-bottom:0;*/border-radius:0.5rem}
|
||||
.ant-collapse>.ant-collapse-item{border-bottom:1px solid rgb(217 217 217 / 20%)}
|
||||
.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0.5rem}
|
||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 16px 12px 40px;color:rgba(0,0,0,.85);line-height:22px;cursor:pointer;transition:all .3s}
|
||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;left:16px;display:inline-block;font-size:12px;transform:translateY(-50%)}
|
||||
.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow>*{line-height:1}
|
||||
@@ -2480,7 +2480,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}
|
||||
.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{right:16px;left:auto}
|
||||
.ant-collapse-anim-active{transition:height .2s cubic-bezier(.215,.61,.355,1)}
|
||||
.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid #d9d9d9}
|
||||
.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid rgb(0 150 112)}
|
||||
.ant-collapse-content>.ant-collapse-content-box{padding:16px}
|
||||
.ant-collapse-content-inactive{display:none}
|
||||
.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 4px 4px}
|
||||
@@ -2539,7 +2539,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar-picker-input{outline:0}
|
||||
.ant-calendar-picker-input.ant-input{line-height:1.5}
|
||||
.ant-calendar-picker-input.ant-input-sm{padding-top:0;padding-bottom:0}
|
||||
.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff}
|
||||
.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:rgb(0 150 112)}
|
||||
.ant-calendar-picker:focus .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-calendar-picker-clear,.ant-calendar-picker-icon{position:absolute;top:50%;right:12px;z-index:1;width:14px;height:14px;margin-top:-7px;font-size:12px;line-height:14px;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-calendar-picker-clear{z-index:2;color:rgba(0,0,0,.25);font-size:14px;background:#fff;cursor:pointer;opacity:0;pointer-events:none}
|
||||
@@ -2549,7 +2549,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-input-disabled+.ant-calendar-picker-icon{cursor:not-allowed}
|
||||
.ant-calendar-picker-small .ant-calendar-picker-clear,.ant-calendar-picker-small .ant-calendar-picker-icon{right:8px}
|
||||
.ant-calendar{position:relative;width:280px;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #fff;border-radius:4px;outline:0;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||||
.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid #e8e8e8}
|
||||
.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid rgb(153 153 153 / 25%)}
|
||||
.ant-calendar-input{width:100%;height:22px;color:rgba(0,0,0,.65);background:#fff;border:0;outline:0;cursor:auto}
|
||||
.ant-calendar-input::-moz-placeholder{color:#bfbfbf;opacity:1}
|
||||
.ant-calendar-input:-ms-input-placeholder{color:#bfbfbf}
|
||||
@@ -2559,7 +2559,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar-input:placeholder-shown{text-overflow:ellipsis}
|
||||
.ant-calendar-week-number{width:286px}
|
||||
.ant-calendar-week-number-cell{text-align:center}
|
||||
.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid rgb(153 153 153 / 25%);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-calendar-header a:hover{color:#40a9ff}
|
||||
.ant-calendar-header .ant-calendar-century-select,.ant-calendar-header .ant-calendar-decade-select,.ant-calendar-header .ant-calendar-month-select,.ant-calendar-header .ant-calendar-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}
|
||||
.ant-calendar-header .ant-calendar-century-select-arrow,.ant-calendar-header .ant-calendar-decade-select-arrow,.ant-calendar-header .ant-calendar-month-select-arrow,.ant-calendar-header .ant-calendar-year-select-arrow{display:none}
|
||||
@@ -2595,8 +2595,8 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar-date{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:22px;text-align:center;background:0 0;border:1px solid transparent;border-radius:2px;transition:background .3s ease}
|
||||
.ant-calendar-date-panel{position:relative;outline:0}
|
||||
.ant-calendar-date:hover{background:#e6f7ff;cursor:pointer}
|
||||
.ant-calendar-date:active{color:#fff;background:#40a9ff}
|
||||
.ant-calendar-today .ant-calendar-date{color:#1890ff;font-weight:700;border-color:#1890ff}
|
||||
.ant-calendar-date:active{color:rgba(0,0,0,.65);background:#bae7ff}
|
||||
.ant-calendar-today .ant-calendar-date{color:rgb(0 150 112);font-weight:700;border-color:rgb(0 150 112)}
|
||||
.ant-calendar-selected-day .ant-calendar-date{background:#bae7ff}
|
||||
.ant-calendar-last-month-cell .ant-calendar-date,.ant-calendar-last-month-cell .ant-calendar-date:hover,.ant-calendar-next-month-btn-day .ant-calendar-date,.ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgba(0,0,0,.25);background:0 0;border-color:transparent}
|
||||
.ant-calendar-disabled-cell .ant-calendar-date{position:relative;width:auto;color:rgba(0,0,0,.25);background:#f5f5f5;border:1px solid transparent;border-radius:0;cursor:not-allowed}
|
||||
@@ -2606,7 +2606,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;border:1px solid rgba(0,0,0,.25);border-radius:2px;content:" "}
|
||||
.ant-calendar-disabled-cell-first-of-row .ant-calendar-date{border-top-left-radius:4px;border-bottom-left-radius:4px}
|
||||
.ant-calendar-disabled-cell-last-of-row .ant-calendar-date{border-top-right-radius:4px;border-bottom-right-radius:4px}
|
||||
.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid #e8e8e8}
|
||||
.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid rgb(153 153 153 / 25%)}
|
||||
.ant-calendar-footer:empty{border-top:0}
|
||||
.ant-calendar-footer-btn{display:block;text-align:center}
|
||||
.ant-calendar-footer-extra{text-align:left}
|
||||
@@ -2616,7 +2616,7 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar .ant-calendar-clear-btn{position:absolute;top:7px;right:5px;display:none;width:20px;height:20px;margin:0;overflow:hidden;line-height:20px;text-align:center;text-indent:-76px}
|
||||
.ant-calendar .ant-calendar-clear-btn:after{display:inline-block;width:20px;color:rgba(0,0,0,.25);font-size:14px;line-height:1;text-indent:43px;transition:color .3s ease}
|
||||
.ant-calendar .ant-calendar-clear-btn:hover:after{color:rgba(0,0,0,.45)}
|
||||
.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;touch-action:manipulation;height:32px;color:#fff;background-color:#1890ff;border:1px solid #1890ff;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:4px;line-height:22px}
|
||||
.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;touch-action:manipulation;height:32px;color:#fff;background-color:rgb(0 150 112);border:1px solid rgb(0 150 112);text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:4px;line-height:22px}
|
||||
.ant-calendar .ant-calendar-ok-btn>.anticon{line-height:1}
|
||||
.ant-calendar .ant-calendar-ok-btn,.ant-calendar .ant-calendar-ok-btn:active,.ant-calendar .ant-calendar-ok-btn:focus{outline:0}
|
||||
.ant-calendar .ant-calendar-ok-btn:not([disabled]):hover{text-decoration:none}
|
||||
@@ -2627,13 +2627,13 @@ to{transform:scale(1.6);opacity:0}
|
||||
.ant-calendar .ant-calendar-ok-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:4px}
|
||||
.ant-calendar .ant-calendar-ok-btn>a:only-child{color:currentColor}
|
||||
.ant-calendar .ant-calendar-ok-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:#40a9ff;border-color:#40a9ff}
|
||||
.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:rgb(0 150 112);border-color:rgb(0 150 112)}
|
||||
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child{color:currentColor}
|
||||
.ant-calendar .ant-calendar-ok-btn:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-calendar .ant-calendar-ok-btn.active,.ant-calendar .ant-calendar-ok-btn:active{color:#fff;background-color:#096dd9;border-color:#096dd9}
|
||||
.ant-calendar .ant-calendar-ok-btn.active>a:only-child,.ant-calendar .ant-calendar-ok-btn:active>a:only-child{color:currentColor}
|
||||
.ant-calendar .ant-calendar-ok-btn.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}
|
||||
.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgb(189 185 185);background-color:rgb(189 189 189 / 10%);border:1px solid rgb(199 199 199 / 50%);text-shadow:none;box-shadow:none}
|
||||
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child{color:currentColor}
|
||||
.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:0 0;content:""}
|
||||
.ant-calendar-range-picker-input{width:44%;height:99%;text-align:center;background-color:transparent;border:0;outline:0}
|
||||
@@ -2944,7 +2944,7 @@ textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-h
|
||||
.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}
|
||||
.ant-tag-lime{color:#a0d911;background:#fcffe6;border-color:#eaff8f}
|
||||
.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}
|
||||
.ant-tag-green{color:#52c41a;background:#f6ffed;border-color:#b7eb8f}
|
||||
.ant-tag-green{color:#19bf95;background:#ebfffa;border-color:#8ce7d0}
|
||||
.ant-tag-green-inverse{color:#fff;background:#52c41a;border-color:#52c41a}
|
||||
.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}
|
||||
.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}
|
||||
@@ -2980,7 +2980,7 @@ textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-h
|
||||
.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.ant-divider-with-text-center,.ant-divider-horizontal.ant-divider-with-text-left,.ant-divider-horizontal.ant-divider-with-text-right{display:table;margin:0 0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-align:center;background:0 0}
|
||||
.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid #e8e8e8;transform:translateY(50%);content:""}
|
||||
.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid rgb(0 150 112 / 50%);transform:translateY(50%);content:""}
|
||||
.ant-divider-horizontal.ant-divider-with-text-left .ant-divider-inner-text,.ant-divider-horizontal.ant-divider-with-text-right .ant-divider-inner-text{display:inline-block;padding:0 10px}
|
||||
.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}
|
||||
.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}
|
||||
@@ -3216,7 +3216,7 @@ to{transform:scale(1)}
|
||||
.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}
|
||||
.ant-input-number:-ms-input-placeholder{text-overflow:ellipsis}
|
||||
.ant-input-number:placeholder-shown{text-overflow:ellipsis}
|
||||
.ant-input-number:focus{border-color:#40a9ff;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-input-number:focus{border-color:rgb(0, 150, 112);border-right-width:1px!important;outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px !important}
|
||||
.ant-input-number[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||
.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||
textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}
|
||||
@@ -3230,7 +3230,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
||||
.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}
|
||||
.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}
|
||||
.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}
|
||||
.ant-input-number-focused,.ant-input-number:hover{border-color:#40a9ff;border-right-width:1px!important}
|
||||
.ant-input-number-focused,.ant-input-number:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
||||
.ant-input-number-focused{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
||||
.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||
.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||
@@ -3261,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-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-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#fff}
|
||||
.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#f0f2f5}
|
||||
.ant-layout,.ant-layout *{box-sizing:border-box}
|
||||
.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}
|
||||
@@ -3270,7 +3270,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
||||
.ant-layout-footer{padding:24px 50px;color:rgba(0,0,0,.65);font-size:14px;background:#f0f2f5}
|
||||
.ant-layout-content{flex:auto;min-height:0}
|
||||
.ant-layout-sider{position:relative;min-width:0;background:#001529;transition:all .2s}
|
||||
.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}
|
||||
.ant-layout-sider-children{height:100%;margin-top:-.1px;padding:0.5rem}
|
||||
.ant-layout-sider-has-trigger{padding-bottom:48px}
|
||||
.ant-layout-sider-right{order:1}
|
||||
.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;transition:all .2s}
|
||||
@@ -3278,7 +3278,7 @@ 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:hover{background:#192c3e}
|
||||
.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:4px 0 0 4px}
|
||||
.ant-layout-sider-light{background:#f7f7f7}
|
||||
.ant-layout-sider-light{background:#fff}
|
||||
.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}
|
||||
.ant-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}
|
||||
@@ -3532,9 +3532,9 @@ to{max-height:0;padding:0;opacity:0}
|
||||
.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:rgba(0,0,0,.45);font-weight:700;line-height:1;text-decoration:none;background:0 0;border:0;outline:0;cursor:pointer;transition:color .3s}
|
||||
.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}
|
||||
.ant-modal-close:focus,.ant-modal-close:hover{color:rgba(0,0,0,.75);text-decoration:none}
|
||||
.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:4px 4px 0 0}
|
||||
.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid rgba(100, 100, 100, 0.2);border-radius:4px 4px 0 0}
|
||||
.ant-modal-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}
|
||||
.ant-modal-footer{padding:10px 16px;text-align:right;background:0 0;border-top:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
||||
.ant-modal-footer{padding:10px 16px;text-align:right;background:0 0;border-top:1px solid rgba(100, 100, 100, 0.2);border-radius:0 0 4px 4px}
|
||||
.ant-modal-footer button+button{margin-bottom:0;margin-left:8px}
|
||||
.ant-modal.zoom-appear,.ant-modal.zoom-enter{transform:none;opacity:0;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:rgba(0,0,0,.45);filter:alpha(opacity=50)}
|
||||
@@ -3673,14 +3673,15 @@ to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}
|
||||
.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{top:12px}
|
||||
.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}
|
||||
.ant-progress{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}
|
||||
.ant-progress{box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;}
|
||||
.ant-progress-line{position:relative;width:100%;font-size:14px}
|
||||
.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}
|
||||
.ant-progress-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-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#f5f5f5;border-radius:100px}
|
||||
.ant-progress-circle-trail{stroke:#090b0e29}
|
||||
.ant-progress-circle-trail{stroke:rgb(100 100 100 / 15%) !important}
|
||||
.ant-progress-circle-path{-webkit-animation:ant-progress-appear .3s;animation:ant-progress-appear .3s}
|
||||
.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#1890ff}
|
||||
.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:rgb(0 150 112) !important}
|
||||
.ant-progress-bg,.ant-progress-success-bg{position:relative;background-color:#1890ff;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}
|
||||
.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#52c41a}
|
||||
.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:rgba(0,0,0,.45);font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}
|
||||
@@ -3968,7 +3969,7 @@ to{background-position:0 50%}
|
||||
.ant-switch-small.ant-switch-checked .ant-switch-inner{margin-right:18px;margin-left:3px}
|
||||
.ant-switch-small.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-13px}
|
||||
.ant-switch-small.ant-switch-loading .ant-switch-loading-icon{font-weight:700;transform:scale(.66667)}
|
||||
.ant-switch-checked{background-color:#1890ff}
|
||||
.ant-switch-checked{background-color:rgb(0 150 112)}
|
||||
.ant-switch-checked .ant-switch-inner{margin-right:24px;margin-left:6px}
|
||||
.ant-switch-checked:after{left:100%;margin-left:-1px;transform:translateX(-100%)}
|
||||
.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-19px}
|
||||
@@ -3990,7 +3991,7 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
||||
.ant-table-empty .ant-table-body{overflow-x:auto!important;overflow-y:hidden!important}
|
||||
.ant-table table{width:100%;text-align:left;border-radius:4px 4px 0 0;border-collapse:separate;border-spacing:0}
|
||||
.ant-table-layout-fixed table{table-layout:fixed}
|
||||
.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:1px solid #e8e8e8;transition:background .3s ease}
|
||||
.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:2px solid rgba(155, 155, 155, 0.15);transition:background .3s ease}
|
||||
.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}
|
||||
.ant-table-thead>tr>th .ant-table-filter-icon,.ant-table-thead>tr>th .anticon-filter{position:absolute;top:0;right:0;width:28px;height:100%;color:#bfbfbf;font-size:12px;text-align:center;cursor:pointer;transition:all .3s}
|
||||
.ant-table-thead>tr>th .ant-table-filter-icon>svg,.ant-table-thead>tr>th .anticon-filter>svg{position:absolute;top:50%;left:50%;margin-top:-5px;margin-left:-6px}
|
||||
@@ -4051,7 +4052,7 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
||||
.ant-table-bordered.ant-table-fixed-header .ant-table-body-inner>table,.ant-table-bordered.ant-table-fixed-header .ant-table-header+.ant-table-body>table{border-top:0}
|
||||
.ant-table-bordered .ant-table-thead>tr:not(:last-child)>th{border-bottom:1px solid #e8e8e8}
|
||||
.ant-table-bordered .ant-table-tbody>tr>td,.ant-table-bordered .ant-table-thead>tr>th{border-right:1px solid #e8e8e8}
|
||||
.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8;border-radius:0 0 4px 4px}
|
||||
.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:2px solid rgba(155, 155, 155, 0.15);border-bottom:2px solid rgba(155, 155, 155, 0.15);border-radius:0 0 4px 4px}
|
||||
.ant-table-pagination.ant-pagination{float:right;margin:16px 0}
|
||||
.ant-table-filter-dropdown{position:relative;min-width:96px;margin-left:-8px;background:#fff;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||||
.ant-table-filter-dropdown .ant-dropdown-menu{max-height:calc(100vh - 130px);overflow-x:hidden;border:0;border-radius:4px 4px 0 0;box-shadow:none}
|
||||
@@ -4074,8 +4075,8 @@ to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}
|
||||
.ant-table-selection-down{display:inline-block;padding:0;line-height:1;cursor:pointer}
|
||||
.ant-table-selection-down:hover .anticon-down{color:rgba(0,0,0,.6)}
|
||||
.ant-table-row-expand-icon{color:#1890ff;text-decoration:none;cursor:pointer;transition:color .3s;display:inline-block;width:17px;height:17px;color:inherit;line-height:13px;text-align:center;background:#fff;border:1px solid #e8e8e8;border-radius:2px;outline:0;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#40a9ff}
|
||||
.ant-table-row-expand-icon:active{color:#096dd9}
|
||||
.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:rgb(0 150 112)}
|
||||
.ant-table-row-expand-icon:active{color:rgb(0 150 112)}
|
||||
.ant-table-row-expand-icon:active,.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{border-color:currentColor}
|
||||
.ant-table-row-expanded:after{content:"-"}
|
||||
.ant-table-row-collapsed:after{content:"+"}
|
||||
@@ -4493,4 +4494,4 @@ to{width:0;height:0;margin:0;padding:0;opacity:0}
|
||||
}
|
||||
@keyframes uploadAnimateInlineOut{
|
||||
to{width:0;height:0;margin:0;padding:0;opacity:0}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
#app {
|
||||
html,
|
||||
body {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ant-space {
|
||||
@@ -11,7 +29,7 @@
|
||||
}
|
||||
|
||||
.ant-card {
|
||||
border-radius: 10px;
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.ant-card-hoverable {
|
||||
@@ -180,13 +198,35 @@
|
||||
|
||||
.ant-card-dark:hover {
|
||||
border-color: #e8e8e8;
|
||||
/* box-shadow: 0 3px 12px -0.8px #0000005c; */
|
||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 70%);
|
||||
}
|
||||
|
||||
.ant-card-bordered:hover {
|
||||
box-shadow: 0 3px 12px -0.8px #0000005c;
|
||||
.ant-setting-textarea {
|
||||
margin-top: 1.5rem;
|
||||
min-height: 300px !important;
|
||||
/*max-height: 800px !important;*/
|
||||
}
|
||||
|
||||
.ant-card-dark-box-nohover{
|
||||
padding: 0 20px 20px !important;
|
||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||
}
|
||||
.ant-card-dark-box-nohover:hover{
|
||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||
/*background-color: rgb(36 44 58 / 50%);*/
|
||||
}
|
||||
|
||||
.ant-card-dark-securitybox-nohover{
|
||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||
}
|
||||
.ant-card-dark-securitybox-nohover:hover{
|
||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||
}
|
||||
|
||||
/* .ant-card-bordered:hover {
|
||||
box-shadow: 0 3px 12px -0.8px #0000005c;
|
||||
} */
|
||||
|
||||
.ant-card-dark .ant-table-thead th {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #161b22;
|
||||
@@ -203,6 +243,7 @@
|
||||
.ant-card-dark .ant-input-group-addon {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #262f3d;
|
||||
border: 1px solid rgb(149 149 149 / 30%);
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-list-item-meta-title,
|
||||
@@ -256,6 +297,15 @@
|
||||
border: 1px solid rgb(0 150 112 / 0%);
|
||||
}
|
||||
|
||||
.ant-layout:not(.login) .ant-input:focus,
|
||||
.ant-layout:not(.login) .ant-input:hover,
|
||||
.ant-layout:not(.login) .ant-input-number:focus,
|
||||
.ant-layout:not(.login) .ant-input-number:hover,
|
||||
.ant-layout:not(.login) .ant-calendar-input:focus,
|
||||
.ant-layout:not(.login) .ant-calendar-input:hover {
|
||||
background-color: rgba(0, 149, 111, 0.1);
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
background-color: #242c3a;
|
||||
@@ -264,11 +314,12 @@
|
||||
.ant-card-dark .ant-collapse-item {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #161b22;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.ant-dropdown-menu-dark,
|
||||
.ant-card-dark .ant-modal-content {
|
||||
border: 1px solid rgba(255, 255, 255, 0.65);
|
||||
border:1px solid rgb(100 100 100 / 20%);
|
||||
box-shadow: 0 2px 8px rgba(255,255,255,.15);
|
||||
}
|
||||
|
||||
@@ -280,7 +331,7 @@
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||
background-color: #1668dc;
|
||||
background-color: rgb(0 150 112);
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
||||
@@ -333,9 +384,9 @@
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-tag-green {
|
||||
color: #6abe39;
|
||||
background: #162312;
|
||||
border-color: #274916;
|
||||
color: #37b998;
|
||||
background: #101e1a;
|
||||
border-color: #144237;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-tag-cyan {
|
||||
@@ -362,7 +413,7 @@
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-switch-checked {
|
||||
background-color: #0c61b0;
|
||||
background-color: #009670;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-btn,
|
||||
@@ -373,19 +424,19 @@
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-radio-button-wrapper:hover {
|
||||
color: #177ddc;
|
||||
color: #009670;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-btn-primary {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #073763;
|
||||
border-color: #1890ff;
|
||||
background-color: rgb(7 98 75 / 50%);
|
||||
border-color: #009670;
|
||||
text-shadow: 0 -1px 0 rgba(255,255,255,.12);
|
||||
box-shadow: 0 2px 0 rgba(255,255,255,.045);
|
||||
}
|
||||
.ant-card-dark .ant-btn-primary:hover {
|
||||
background-color: #40a9ff;
|
||||
border-color: #40a9ff;
|
||||
background-color: #009670;
|
||||
border-color: #40a9ff00;
|
||||
}
|
||||
|
||||
.ant-dark .ant-popover-content {
|
||||
@@ -408,15 +459,18 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: 0.7em;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(50 62 82 / 25%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #11141a57;
|
||||
border-radius: 20px;
|
||||
background: rgb(133 133 133 / 80%);
|
||||
border-radius: 100vw;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #11141a66;
|
||||
border-radius: 20px;
|
||||
}
|
||||
background: #919191;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded
|
||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
(config) => {
|
||||
if (config.data instanceof FormData) {
|
||||
config.headers['Content-Type'] = 'multipart/form-data';
|
||||
} else {
|
||||
@@ -12,5 +12,5 @@ axios.interceptors.request.use(
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
(error) => Promise.reject(error),
|
||||
);
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
supportLangs = [
|
||||
const supportLangs = [
|
||||
{
|
||||
name : "English",
|
||||
value : "en-US",
|
||||
icon : "🇺🇸"
|
||||
name: 'English',
|
||||
value: 'en-US',
|
||||
icon: '🇺🇸',
|
||||
},
|
||||
{
|
||||
name : "Farsi",
|
||||
value : "fa_IR",
|
||||
icon : "🇮🇷"
|
||||
name: 'Farsi',
|
||||
value: 'fa_IR',
|
||||
icon: '🇮🇷',
|
||||
},
|
||||
{
|
||||
name : "汉语",
|
||||
value : "zh-Hans",
|
||||
icon : "🇨🇳"
|
||||
name: '汉语',
|
||||
value: 'zh-Hans',
|
||||
icon: '🇨🇳',
|
||||
},
|
||||
]
|
||||
{
|
||||
name: 'Russian',
|
||||
value: 'ru_RU',
|
||||
icon: '🇷🇺',
|
||||
},
|
||||
];
|
||||
|
||||
function getLang(){
|
||||
let lang = getCookie('lang')
|
||||
function getLang() {
|
||||
let lang = getCookie('lang');
|
||||
|
||||
if (! lang){
|
||||
if (window.navigator){
|
||||
if (!lang) {
|
||||
if (window.navigator) {
|
||||
lang = window.navigator.language || window.navigator.userLanguage;
|
||||
|
||||
if (isSupportLang(lang)){
|
||||
setCookie('lang' , lang , 150)
|
||||
}else{
|
||||
setCookie('lang' , 'en-US' , 150)
|
||||
if (isSupportLang(lang)) {
|
||||
setCookie('lang', lang, 150);
|
||||
} else {
|
||||
setCookie('lang', 'en-US', 150);
|
||||
window.location.reload();
|
||||
}
|
||||
}else{
|
||||
setCookie('lang' , 'en-US' , 150)
|
||||
} else {
|
||||
setCookie('lang', 'en-US', 150);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -38,47 +43,21 @@ function getLang(){
|
||||
return lang;
|
||||
}
|
||||
|
||||
function setLang(lang){
|
||||
|
||||
if (!isSupportLang(lang)){
|
||||
function setLang(lang) {
|
||||
if (!isSupportLang(lang)) {
|
||||
lang = 'en-US';
|
||||
}
|
||||
|
||||
setCookie('lang' , lang , 150)
|
||||
setCookie('lang', lang, 150);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function isSupportLang(lang){
|
||||
for (l of supportLangs){
|
||||
if (l.value === lang){
|
||||
function isSupportLang(lang) {
|
||||
for (l of supportLangs) {
|
||||
if (l.value === lang) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getCookie(cname) {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i <ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
let expires = "expires="+ d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
@@ -1487,7 +1487,7 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||
}
|
||||
|
||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
||||
const url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
url.searchParams.set(key, value)
|
||||
|
||||
@@ -56,14 +56,37 @@ function toFixed(num, n) {
|
||||
return Math.round(num * n) / n;
|
||||
}
|
||||
|
||||
function debounce (fn, delay) {
|
||||
var timeoutID = null
|
||||
function debounce(fn, delay) {
|
||||
var timeoutID = null;
|
||||
return function () {
|
||||
clearTimeout(timeoutID)
|
||||
var args = arguments
|
||||
var that = this
|
||||
timeoutID = setTimeout(function () {
|
||||
fn.apply(that, args)
|
||||
}, delay)
|
||||
clearTimeout(timeoutID);
|
||||
var args = arguments;
|
||||
var that = this;
|
||||
timeoutID = setTimeout(function () {
|
||||
fn.apply(that, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
let name = cname + '=';
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
let expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/';
|
||||
}
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
const oneMinute = 1000 * 60; // 一分钟的毫秒数
|
||||
const oneHour = oneMinute * 60; // 一小时的毫秒数
|
||||
const oneDay = oneHour * 24; // 一天的毫秒数
|
||||
const oneWeek = oneDay * 7; // 一星期的毫秒数
|
||||
const oneMonth = oneDay * 30; // 一个月的毫秒数
|
||||
const oneMinute = 1000 * 60; // MilliseConds in a Minute
|
||||
const oneHour = oneMinute * 60; // The milliseconds of one hour
|
||||
const oneDay = oneHour * 24; // The Number of MilliseConds A Day
|
||||
const oneWeek = oneDay * 7; // The milliseconds per week
|
||||
const oneMonth = oneDay * 30; // The milliseconds of a month
|
||||
|
||||
/**
|
||||
* 按天数减少
|
||||
* Decrease according to the number of days
|
||||
*
|
||||
* @param days 要减少的天数
|
||||
* @param days to reduce the number of days to be reduced
|
||||
*/
|
||||
Date.prototype.minusDays = function (days) {
|
||||
return this.minusMillis(oneDay * days);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按天数增加
|
||||
* Increase according to the number of days
|
||||
*
|
||||
* @param days 要增加的天数
|
||||
* @param days The number of days to be increased
|
||||
*/
|
||||
Date.prototype.plusDays = function (days) {
|
||||
return this.plusMillis(oneDay * days);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按小时减少
|
||||
* A few
|
||||
*
|
||||
* @param hours 要减少的小时数
|
||||
* @param hours to be reduced
|
||||
*/
|
||||
Date.prototype.minusHours = function (hours) {
|
||||
return this.minusMillis(oneHour * hours);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按小时增加
|
||||
* Increase hourly
|
||||
*
|
||||
* @param hours 要增加的小时数
|
||||
* @param hours to increase the number of hours
|
||||
*/
|
||||
Date.prototype.plusHours = function (hours) {
|
||||
return this.plusMillis(oneHour * hours);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按分钟减少
|
||||
* Make reduction in minutes
|
||||
*
|
||||
* @param minutes 要减少的分钟数
|
||||
* @param minutes to reduce the number of minutes
|
||||
*/
|
||||
Date.prototype.minusMinutes = function (minutes) {
|
||||
return this.minusMillis(oneMinute * minutes);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按分钟增加
|
||||
* Add in minutes
|
||||
*
|
||||
* @param minutes 要增加的分钟数
|
||||
* @param minutes to increase the number of minutes
|
||||
*/
|
||||
Date.prototype.plusMinutes = function (minutes) {
|
||||
return this.plusMillis(oneMinute * minutes);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按毫秒减少
|
||||
* Decrease in milliseconds
|
||||
*
|
||||
* @param millis 要减少的毫秒数
|
||||
* @param millis to reduce the milliseconds
|
||||
*/
|
||||
Date.prototype.minusMillis = function(millis) {
|
||||
let time = this.getTime() - millis;
|
||||
@@ -71,9 +71,9 @@ Date.prototype.minusMillis = function(millis) {
|
||||
};
|
||||
|
||||
/**
|
||||
* 按毫秒增加
|
||||
* Add in milliseconds to increase
|
||||
*
|
||||
* @param millis 要增加的毫秒数
|
||||
* @param millis to increase the milliseconds to increase
|
||||
*/
|
||||
Date.prototype.plusMillis = function(millis) {
|
||||
let time = this.getTime() + millis;
|
||||
@@ -83,7 +83,7 @@ Date.prototype.plusMillis = function(millis) {
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置时间为当天的 00:00:00.000
|
||||
* Setting time is 00: 00: 00.000 on the day
|
||||
*/
|
||||
Date.prototype.setMinTime = function () {
|
||||
this.setHours(0);
|
||||
@@ -94,7 +94,7 @@ Date.prototype.setMinTime = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置时间为当天的 23:59:59.999
|
||||
* Setting time is 23: 59: 59.999 on the same day
|
||||
*/
|
||||
Date.prototype.setMaxTime = function () {
|
||||
this.setHours(23);
|
||||
@@ -105,37 +105,36 @@ Date.prototype.setMaxTime = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
* Formatting date
|
||||
*/
|
||||
Date.prototype.formatDate = function () {
|
||||
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate());
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* Format time
|
||||
*/
|
||||
Date.prototype.formatTime = function () {
|
||||
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds());
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化日期加时间
|
||||
* Formatting date plus time
|
||||
*
|
||||
* @param split 日期和时间之间的分隔符,默认是一个空格
|
||||
* @param split Date and time separation symbols, default is a space
|
||||
*/
|
||||
Date.prototype.formatDateTime = function (split = ' ') {
|
||||
return this.formatDate() + split + this.formatTime();
|
||||
};
|
||||
|
||||
class DateUtil {
|
||||
|
||||
// 字符串转 Date 对象
|
||||
// String string to date object
|
||||
static parseDate(str) {
|
||||
return new Date(str.replace(/-/g, '/'));
|
||||
}
|
||||
|
||||
static formatMillis(millis) {
|
||||
return moment(millis).format('YYYY-M-D H:m:s')
|
||||
return moment(millis).format('YYYY-M-D H:m:s');
|
||||
}
|
||||
|
||||
static firstDayOfMonth() {
|
||||
@@ -144,4 +143,4 @@ class DateUtil {
|
||||
date.setMinTime();
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,13 +68,11 @@ class HttpUtil {
|
||||
}
|
||||
|
||||
class PromiseUtil {
|
||||
|
||||
static async sleep(timeout) {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, timeout)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const seq = [
|
||||
@@ -95,7 +93,6 @@ const shortIdSeq = [
|
||||
];
|
||||
|
||||
class RandomUtil {
|
||||
|
||||
static randomIntRange(min, max) {
|
||||
return parseInt(Math.random() * (max - min) + min, 10);
|
||||
}
|
||||
@@ -153,8 +150,7 @@ class RandomUtil {
|
||||
static randomText() {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = 6 + Math.floor(Math.random() * 5)
|
||||
for(var ii=0; ii<len; ii++){
|
||||
for (var ii = 0; ii < 8; ii++) {
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return string;
|
||||
@@ -162,11 +158,11 @@ class RandomUtil {
|
||||
|
||||
static randowShortId() {
|
||||
let str = '';
|
||||
str += this.randomShortIdSeq(8)
|
||||
str += this.randomShortIdSeq(8);
|
||||
return str;
|
||||
}
|
||||
|
||||
static randomShadowsocksPassword(){
|
||||
|
||||
static randomShadowsocksPassword() {
|
||||
let array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
return btoa(String.fromCharCode.apply(null, array));
|
||||
@@ -174,7 +170,6 @@ class RandomUtil {
|
||||
}
|
||||
|
||||
class ObjectUtil {
|
||||
|
||||
static getPropIgnoreCase(obj, prop) {
|
||||
for (const name in obj) {
|
||||
if (!obj.hasOwnProperty(name)) {
|
||||
@@ -322,5 +317,4 @@ class ObjectUtil {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
||||
|
||||
func (a *IndexController) index(c *gin.Context) {
|
||||
if session.IsLogin(c) {
|
||||
c.Redirect(http.StatusTemporaryRedirect, "xui/")
|
||||
c.Redirect(http.StatusTemporaryRedirect, "panel/")
|
||||
return
|
||||
}
|
||||
html(c, "login.html", "pages.login.title", nil)
|
||||
@@ -77,9 +77,11 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
logger.Infof("Unable to get session's max age from DB")
|
||||
}
|
||||
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Infof("Unable to set session's max age")
|
||||
if sessionMaxAge > 0 {
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Infof("Unable to set session's max age")
|
||||
}
|
||||
}
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
@@ -101,5 +103,4 @@ func (a *IndexController) getSecretStatus(c *gin.Context) {
|
||||
if err == nil {
|
||||
jsonObj(c, status, nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
||||
}
|
||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||
loginUser := session.GetLoginUser(c)
|
||||
user := a.userService.GetUserSecret(loginUser.Id)
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewXUIController(g *gin.RouterGroup) *XUIController {
|
||||
}
|
||||
|
||||
func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/xui")
|
||||
g = g.Group("/panel")
|
||||
g.Use(a.checkLogin)
|
||||
|
||||
g.GET("/", a.index)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "promptModal"}}
|
||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||
v-model="promptModal.value"
|
||||
@@ -36,12 +36,12 @@
|
||||
},
|
||||
confirm() {},
|
||||
open({
|
||||
title='',
|
||||
type='text',
|
||||
value='',
|
||||
okText='{{ i18n "sure"}}',
|
||||
confirm=() => {},
|
||||
}) {
|
||||
title = '',
|
||||
type = 'text',
|
||||
value = '',
|
||||
okText = '{{ i18n "sure"}}',
|
||||
confirm = () => {},
|
||||
}) {
|
||||
this.title = title;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{{define "qrcodeModal"}}
|
||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||
:closable="true"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:footer="null"
|
||||
width="300px">
|
||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||
</a-tag>
|
||||
<a-tag v-if="qrModal.clientName" color="orange" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||
{{ i18n "pages.inbounds.email" }}: "[[ qrModal.clientName ]]"
|
||||
</a-tag>
|
||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%; margin-top: 10px;"></canvas>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
@@ -16,14 +21,16 @@
|
||||
inbound: new Inbound(),
|
||||
dbInbound: new DBInbound(),
|
||||
copyText: '',
|
||||
clientName: null,
|
||||
qrcode: null,
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
show: function (title='', content='', dbInbound=new DBInbound(), copyText='') {
|
||||
show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.dbInbound = dbInbound;
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clientName = clientName;
|
||||
if (ObjectUtil.isEmpty(copyText)) {
|
||||
this.copyText = content;
|
||||
} else {
|
||||
@@ -48,6 +55,7 @@
|
||||
};
|
||||
|
||||
const qrModalApp = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#qrcode-modal',
|
||||
data: {
|
||||
qrModal: qrModal,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "textModal"}}
|
||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||
@@ -21,7 +21,7 @@
|
||||
qrcode: null,
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
show: function (title='', content='', fileName='') {
|
||||
show: function (title = '', content = '', fileName = '') {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.fileName = fileName;
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.ant-input-group-addon {
|
||||
border-radius: 0 30px 30px 0;
|
||||
width: 50px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper .ant-input-prefix {
|
||||
left: 23px;
|
||||
}
|
||||
@@ -26,20 +32,26 @@
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.selectLang{
|
||||
.centered {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
<a-layout id="app" v-cloak class="login" :class="themeSwitcher.darkCardClass">
|
||||
<transition name="list" appear>
|
||||
<a-layout-content>
|
||||
<a-row type="flex" justify="center">
|
||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
||||
<h1>{{ i18n "pages.login.title" }}</h1>
|
||||
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row type="flex" justify="center">
|
||||
@@ -48,35 +60,33 @@
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||
@keydown.enter.native="login" autofocus>
|
||||
<a-icon slot="prefix" type="user" style="color: rgba(0,0,0,.25)"/>
|
||||
<a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-input type="password" v-model.trim="user.password"
|
||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||
<a-icon slot="prefix" type="lock" style="color: rgba(0,0,0,.25)"/>
|
||||
</a-input>
|
||||
<password-input icon="lock" v-model.trim="user.password"
|
||||
placeholder='{{ i18n "password" }}' @keydown.enter.native="login">
|
||||
</password-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="secretEnable">
|
||||
<a-input type="text" placeholder='{{ i18n "secretToken" }}' v-model.trim="user.loginSecret" @keydown.enter.native="login">
|
||||
<a-icon slot="prefix" type="key" style="color: rgba(0,0,0,.25)"/>
|
||||
<password-input icon="key" v-model.trim="user.loginSecret"
|
||||
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
||||
</password-input>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button block @click="login" :loading="loading">{{ i18n "login" }}</a-button>
|
||||
<a-row justify="center" class="centered">
|
||||
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||
:style="loading ? { width: '50px' } : { display: 'block', width: '100%' }">
|
||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||
</a-button>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
||||
<a-row justify="center" class="selectLang">
|
||||
<a-col :span="5"><span>Language :</span></a-col>
|
||||
|
||||
<a-col :span="7">
|
||||
<a-select
|
||||
ref="selectLang"
|
||||
v-model="lang"
|
||||
@change="setLang(lang)"
|
||||
>
|
||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs" >
|
||||
<a-row justify="center" class="centered">
|
||||
<a-col :span="12">
|
||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||
<span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
@@ -84,6 +94,11 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="center" class="centered">
|
||||
<theme-switch />
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -91,24 +106,24 @@
|
||||
</transition>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/password" .}}
|
||||
<script>
|
||||
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
||||
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
||||
const deg = RandomUtil.randomIntRange(0, 360);
|
||||
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
||||
document.querySelector('#app').style.background = background;
|
||||
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
themeSwitcher,
|
||||
loading: false,
|
||||
user: new User(),
|
||||
secretEnable: false,
|
||||
lang : ""
|
||||
lang: ""
|
||||
},
|
||||
created(){
|
||||
this.lang = getLang();
|
||||
this.secretEnable = this.getSecretStatus();
|
||||
async created() {
|
||||
this.updateBackground();
|
||||
this.lang = getLang();
|
||||
this.secretEnable = await this.getSecretStatus();
|
||||
},
|
||||
methods: {
|
||||
async login() {
|
||||
@@ -116,20 +131,33 @@
|
||||
const msg = await HttpUtil.post('/login', this.user);
|
||||
this.loading = false;
|
||||
if (msg.success) {
|
||||
location.href = basePath + 'xui/';
|
||||
location.href = basePath + 'panel/';
|
||||
}
|
||||
},
|
||||
async getSecretStatus() {
|
||||
this.loading= true;
|
||||
this.loading = true;
|
||||
const msg = await HttpUtil.post('/getSecretStatus');
|
||||
this.loading = false;
|
||||
if (msg.success){
|
||||
if (msg.success) {
|
||||
this.secretEnable = msg.obj;
|
||||
return msg.obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateBackground() {
|
||||
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
||||
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
||||
const deg = RandomUtil.randomIntRange(0, 360);
|
||||
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
||||
document.querySelector('#app').style.background = this.themeSwitcher.isDarkTheme ? colors.dark.bg : background;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'themeSwitcher.isDarkTheme'(newVal, oldVal) {
|
||||
this.updateBackground();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +1,11 @@
|
||||
{{define "clientsBulkModal"}}
|
||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option :value="0">Random</a-select-option>
|
||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||
@@ -71,20 +71,20 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -104,7 +104,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -113,7 +113,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -143,37 +143,42 @@
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
clients = [];
|
||||
method=clientsBulkModal.emailMethod;
|
||||
if(method>1){
|
||||
start=clientsBulkModal.firstNum;
|
||||
end=clientsBulkModal.lastNum + 1;
|
||||
method = clientsBulkModal.emailMethod;
|
||||
if (method > 1) {
|
||||
start = clientsBulkModal.firstNum;
|
||||
end = clientsBulkModal.lastNum + 1;
|
||||
} else {
|
||||
start=0;
|
||||
end=clientsBulkModal.quantity;
|
||||
start = 0;
|
||||
end = clientsBulkModal.quantity;
|
||||
}
|
||||
prefix = (method>0 && clientsBulkModal.emailPrefix.length>0) ? clientsBulkModal.emailPrefix : "";
|
||||
useNum=(method>1);
|
||||
postfix = (method>2 && clientsBulkModal.emailPostfix.length>0) ? clientsBulkModal.emailPostfix : "";
|
||||
prefix = (method > 0 && clientsBulkModal.emailPrefix.length > 0) ? clientsBulkModal.emailPrefix : "";
|
||||
useNum = (method > 1);
|
||||
postfix = (method > 2 && clientsBulkModal.emailPostfix.length > 0) ? clientsBulkModal.emailPostfix : "";
|
||||
for (let i = start; i < end; i++) {
|
||||
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
||||
if(method==4) newClient.email = "";
|
||||
if (method == 4) newClient.email = "";
|
||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||
newClient.subId = clientsBulkModal.subId;
|
||||
newClient.tgId = clientsBulkModal.tgId;
|
||||
newClient.limitIp = clientsBulkModal.limitIp;
|
||||
newClient._totalGB = clientsBulkModal.totalGB;
|
||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||
if (clientsBulkModal.inbound.canEnableTlsFlow()) {
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
if(clientsBulkModal.inbound.xtls){
|
||||
if (clientsBulkModal.inbound.xtls) {
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
clients.push(newClient);
|
||||
}
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||
show({
|
||||
title = '',
|
||||
okText = '{{ i18n "sure" }}',
|
||||
dbInbound = null,
|
||||
confirm = (inbound, dbInbound) => { }
|
||||
}) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
@@ -181,21 +186,21 @@
|
||||
this.quantity = 1;
|
||||
this.totalGB = 0;
|
||||
this.expiryTime = 0;
|
||||
this.emailMethod= 0;
|
||||
this.limitIp= 0;
|
||||
this.firstNum= 1;
|
||||
this.lastNum= 1;
|
||||
this.emailPrefix= "";
|
||||
this.emailPostfix= "";
|
||||
this.subId= "";
|
||||
this.tgId= "";
|
||||
this.flow= "";
|
||||
this.emailMethod = 0;
|
||||
this.limitIp = 0;
|
||||
this.firstNum = 1;
|
||||
this.lastNum = 1;
|
||||
this.emailPrefix = "";
|
||||
this.emailPostfix = "";
|
||||
this.subId = "";
|
||||
this.tgId = "";
|
||||
this.flow = "";
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clientSettings.vmesses;
|
||||
case Protocols.VLESS: return clientSettings.vlesses;
|
||||
case Protocols.TROJAN: return clientSettings.trojans;
|
||||
@@ -230,10 +235,11 @@
|
||||
get delayedExpireDays() {
|
||||
return this.clientsBulkModal.expiryTime < 0 ? this.clientsBulkModal.expiryTime / -86400000 : 0;
|
||||
},
|
||||
set delayedExpireDays(days){
|
||||
set delayedExpireDays(days) {
|
||||
this.clientsBulkModal.expiryTime = -86400000 * days;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "clientsModal"}}
|
||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||
{{template "form/client"}}
|
||||
</a-modal>
|
||||
@@ -23,13 +23,13 @@
|
||||
isExpired: false,
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
if(clientModal.isEdit){
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
@@ -40,11 +40,11 @@
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
||||
this.delayedStart = false;
|
||||
if (isEdit){
|
||||
if (this.clients[index].expiryTime < 0){
|
||||
if (isEdit) {
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]);
|
||||
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
} else {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
this.confirm = confirm;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clientSettings.vmesses;
|
||||
case Protocols.VLESS: return clientSettings.vlesses;
|
||||
case Protocols.TROJAN: return clientSettings.trojans;
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
case Protocols.SHADOWSOCKS: return client.email;
|
||||
default: return client.id;
|
||||
@@ -103,39 +103,30 @@
|
||||
return this.clientModal.isEdit;
|
||||
},
|
||||
get isTrafficExhausted() {
|
||||
if(!clientStats) return false
|
||||
if(clientStats.total <= 0) return false
|
||||
if(clientStats.up + clientStats.down < clientStats.total) return false
|
||||
if (!clientStats) return false
|
||||
if (clientStats.total <= 0) return false
|
||||
if (clientStats.up + clientStats.down < clientStats.total) return false
|
||||
return true
|
||||
},
|
||||
get isExpiry() {
|
||||
return this.clientModal.isExpired
|
||||
},
|
||||
get statsColor() {
|
||||
if(!clientStats) return 'blue'
|
||||
if(clientStats.total <= 0) return 'blue'
|
||||
else if(clientStats.total > 0 && (clientStats.down+clientStats.up) < clientStats.total) return 'cyan'
|
||||
if (!clientStats) return 'blue'
|
||||
if (clientStats.total <= 0) return 'blue'
|
||||
else if (clientStats.total > 0 && (clientStats.down + clientStats.up) < clientStats.total) return 'cyan'
|
||||
else return 'red'
|
||||
},
|
||||
get delayedExpireDays() {
|
||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||
},
|
||||
set delayedExpireDays(days){
|
||||
set delayedExpireDays(days) {
|
||||
this.client.expiryTime = -86400000 * days;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = 6 + Math.floor(Math.random() * 5);
|
||||
for(var ii=0; ii<len; ii++){
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
client.email = string;
|
||||
},
|
||||
async getDBClientIps(email,event) {
|
||||
const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email);
|
||||
async getDBClientIps(email, event) {
|
||||
const msg = await HttpUtil.post('/panel/inbound/clientIps/' + email);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
@@ -149,22 +140,22 @@
|
||||
}
|
||||
},
|
||||
async clearDBClientIps(email) {
|
||||
const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
|
||||
const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
document.getElementById("clientIPs").value = ""
|
||||
},
|
||||
resetClientTraffic(email,dbInboundId,iconElement) {
|
||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: async () => {
|
||||
iconElement.disabled = true;
|
||||
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email);
|
||||
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||
if (msg.success) {
|
||||
this.clientModal.clientStats.up = 0;
|
||||
this.clientModal.clientStats.down = 0;
|
||||
@@ -175,5 +166,6 @@
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{{define "menuItems"}}
|
||||
<a-menu-item key="{{ .base_path }}xui/">
|
||||
<a-menu-item key="{{ .base_path }}panel/">
|
||||
<a-icon type="dashboard"></a-icon>
|
||||
<span>{{ i18n "menu.dashboard"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/inbounds">
|
||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||
<a-icon type="user"></a-icon>
|
||||
<span>{{ i18n "menu.inbounds"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/settings">
|
||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||
<a-icon type="setting"></a-icon>
|
||||
<span>{{ i18n "menu.settings"}}</span>
|
||||
</a-menu-item>
|
||||
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||
<!-- <a-icon type="laptop"></a-icon>-->
|
||||
<!-- <span>Client</span>-->
|
||||
<!--</a-menu-item>-->
|
||||
@@ -23,17 +23,14 @@
|
||||
|
||||
|
||||
{{define "commonSider"}}
|
||||
<a-layout-sider :theme="siderDrawer.theme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||
<a-menu-item mode="inline">
|
||||
<a-icon type="bg-colors"></a-icon>
|
||||
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
||||
checked-children="☀"
|
||||
un-checked-children="🌙"
|
||||
@change="siderDrawer.changeTheme()"></a-switch>
|
||||
<theme-switch />
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
@@ -41,32 +38,25 @@
|
||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||
@close="siderDrawer.close()"
|
||||
:visible="siderDrawer.visible"
|
||||
:wrap-class-name="siderDrawer.isDarkTheme ? 'ant-drawer-dark' : ''"
|
||||
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
||||
:wrap-style="{ padding: 0 }">
|
||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||
</div>
|
||||
<a-menu :theme="siderDrawer.theme" mode="inline" selected-keys="">
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||
<a-menu-item mode="inline">
|
||||
<a-icon type="bg-colors"></a-icon>
|
||||
<a-switch :default-checked="siderDrawer.isDarkTheme"
|
||||
checked-children="☀"
|
||||
un-checked-children="🌙"
|
||||
@change="siderDrawer.changeTheme()"></a-switch>
|
||||
<theme-switch />
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-menu :theme="siderDrawer.theme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
|
||||
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
</a-drawer>
|
||||
<script>
|
||||
const darkClass = "ant-card-dark";
|
||||
const bgDarkStyle = "background-color: #242c3a";
|
||||
const siderDrawer = {
|
||||
visible: false,
|
||||
collapsed: false,
|
||||
isDarkTheme: localStorage.getItem("dark-mode") === 'false' ? false : true,
|
||||
show() {
|
||||
this.visible = true;
|
||||
},
|
||||
@@ -76,17 +66,6 @@
|
||||
change() {
|
||||
this.visible = !this.visible;
|
||||
},
|
||||
toggleCollapsed() {
|
||||
this.collapsed = !this.collapsed;
|
||||
},
|
||||
changeTheme() {
|
||||
this.isDarkTheme = ! this.isDarkTheme;
|
||||
localStorage.setItem("dark-mode", this.isDarkTheme);
|
||||
},
|
||||
get theme() {
|
||||
return this.isDarkTheme ? 'dark' : 'light';
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
35
web/html/xui/component/password.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{{define "component/passwordInput"}}
|
||||
<template>
|
||||
<a-input :value="value" :type="showPassword ? 'text' : 'password'"
|
||||
:placeholder="placeholder"
|
||||
@input="$emit('input', $event.target.value)">
|
||||
<template v-if="icon" #prefix>
|
||||
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||
@click="toggleShowPassword"
|
||||
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/password"}}
|
||||
<script>
|
||||
Vue.component('password-input', {
|
||||
props: ["title", "value", "placeholder", "icon"],
|
||||
template: `{{template "component/passwordInput"}}`,
|
||||
data() {
|
||||
return {
|
||||
showPassword: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleShowPassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,6 +1,13 @@
|
||||
{{define "component/settingListItem"}}
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-row v-if="type === 'textarea'">
|
||||
<a-col>
|
||||
<a-list-item-meta :title="title" :description="desc"/>
|
||||
<a-textarea class="ant-setting-textarea" :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10 }"></a-textarea>
|
||||
<!--a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 30 }"></a-textarea-->
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta :title="title" :description="desc"/>
|
||||
</a-col>
|
||||
@@ -11,9 +18,6 @@
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'textarea'">
|
||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||
</template>
|
||||
<template v-else-if="type === 'switch'">
|
||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||
</template>
|
||||
@@ -29,4 +33,4 @@
|
||||
template: `{{template "component/settingListItem"}}`,
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
58
web/html/xui/component/themeSwitch.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{{define "component/themeSwitchTemplate"}}
|
||||
<template>
|
||||
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
||||
checked-children="☀"
|
||||
un-checked-children="🌙"
|
||||
@change="themeSwitcher.toggleTheme()">
|
||||
</a-switch>
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/themeSwitcher"}}
|
||||
<script>
|
||||
const colors = {
|
||||
dark: {
|
||||
bg: "#242c3a",
|
||||
text: "hsla(0,0%,100%,.65)"
|
||||
},
|
||||
light: {
|
||||
bg: '#f0f2f5',
|
||||
text: "rgba(0, 0, 0, 0.7)",
|
||||
}
|
||||
}
|
||||
|
||||
function createThemeSwitcher() {
|
||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||
const theme = isDarkTheme ? 'dark' : 'light';
|
||||
return {
|
||||
isDarkTheme,
|
||||
bgStyle: `background: ${colors[theme].bg};`,
|
||||
textStyle: `color: ${colors[theme].text};`,
|
||||
darkClass: isDarkTheme ? 'ant-dark' : '',
|
||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
||||
get currentTheme() {
|
||||
return this.isDarkTheme ? 'dark' : 'light';
|
||||
},
|
||||
toggleTheme() {
|
||||
this.isDarkTheme = !this.isDarkTheme;
|
||||
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
||||
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const themeSwitcher = createThemeSwitcher();
|
||||
|
||||
Vue.component('theme-switch', {
|
||||
props: [],
|
||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||
data: () => ({ themeSwitcher }),
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,7 +1,9 @@
|
||||
{{define "form/client"}}
|
||||
<a-form layout="inline" v-if="client">
|
||||
<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>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
@@ -9,25 +11,27 @@
|
||||
<br>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;" ></a-input>
|
||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.password" style="width: 300px;" ></a-input>
|
||||
<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>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
||||
<a-input-number v-model="client.alterId"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
@@ -40,8 +44,9 @@
|
||||
<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-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
Telegram ID
|
||||
@@ -85,26 +90,29 @@
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-form layout="block">
|
||||
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 2, maxRows: 10 }">
|
||||
<a-textarea id="clientIPs" readonly
|
||||
@click="getDBClientIps(client.email,$event)"
|
||||
placeholder="Click To Get IPs"
|
||||
:auto-size="{ minRows: 2, maxRows: 10 }">
|
||||
</a-textarea>
|
||||
</a-form>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -112,9 +120,10 @@
|
||||
<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-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
<template v-if="isEdit && clientStats">
|
||||
<br><span> {{ i18n "usage" }}:</span>
|
||||
<br>
|
||||
<span> {{ i18n "usage" }}:</span>
|
||||
<a-tag :color="statsColor">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
@@ -122,7 +131,8 @@
|
||||
</a-tag>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||
v-if="client.email.length > 0"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-form-item>
|
||||
@@ -136,7 +146,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -145,7 +155,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "protocol" }}'>
|
||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -31,7 +31,7 @@
|
||||
<br>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -43,7 +43,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -52,7 +52,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="udp">UDP</a-select-option>
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
<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-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Password">
|
||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
@@ -28,8 +28,9 @@
|
||||
<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-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
Telegram ID
|
||||
@@ -57,7 +58,7 @@
|
||||
<br>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -77,7 +78,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -86,7 +87,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
@@ -106,7 +107,7 @@
|
||||
</a-form>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "encryption" }}'>
|
||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -114,7 +115,7 @@
|
||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="udp">UDP</a-select-option>
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.settings.udp"
|
||||
label="IP">
|
||||
<a-form-item v-if="inbound.settings.udp" label="IP">
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Password">
|
||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
||||
@@ -27,7 +27,8 @@
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.subId"></a-input>
|
||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
@@ -55,14 +56,14 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -82,7 +83,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -91,7 +92,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
@@ -113,8 +114,7 @@
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-row>
|
||||
<a-button type="primary" size="small"
|
||||
@click="inbound.settings.addTrojanFallback()">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addTrojanFallback()">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="ID">
|
||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
@@ -27,8 +28,9 @@
|
||||
<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-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
Telegram ID
|
||||
@@ -55,20 +57,20 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -88,7 +90,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -97,7 +99,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
@@ -119,8 +121,7 @@
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-row>
|
||||
<a-button type="primary" size="small"
|
||||
@click="inbound.settings.addFallback()">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="getNewEmail(client)"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-input v-model.trim="client.email" style="width: 150px;"></a-input>
|
||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
||||
@@ -20,6 +20,7 @@
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label="ID">
|
||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
@@ -32,8 +33,9 @@
|
||||
<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-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email">
|
||||
<span slot="label">
|
||||
Telegram ID
|
||||
@@ -61,7 +63,7 @@
|
||||
<br>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
@@ -81,7 +83,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<span slot="label">
|
||||
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
@@ -90,7 +92,7 @@
|
||||
</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' : ''"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{{define "form/sniffing"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
Sniffing
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
Sniffing
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/streamKCP"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
||||
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "form/streamQUIC"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
@@ -11,7 +11,7 @@
|
||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- select stream network -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">KCP</a-select-option>
|
||||
<a-select-option value="ws">WS</a-select-option>
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
</a-form>
|
||||
|
||||
<!-- tcp request -->
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||
</a-form-item>
|
||||
@@ -28,8 +27,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
@@ -39,8 +37,7 @@
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.request.removeHeader(index)">
|
||||
<a-button size="small" @click="inbound.stream.tcp.request.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
@@ -50,8 +47,7 @@
|
||||
</a-form>
|
||||
|
||||
<!-- tcp response -->
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'"
|
||||
layout="inline">
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||
</a-form-item>
|
||||
@@ -63,8 +59,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||
<a-button size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
@@ -74,8 +69,7 @@
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.removeHeader(index)">
|
||||
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||
<a-row>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.addHeader('Host', '')">
|
||||
<a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">
|
||||
+
|
||||
</a-button>
|
||||
</a-row>
|
||||
@@ -21,8 +20,7 @@
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.ws.removeHeader(index)">
|
||||
<a-button size="small" @click="inbound.stream.ws.removeHeader(index)">
|
||||
-
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
@@ -37,18 +37,18 @@
|
||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="CipherSuites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="">auto</a-select-option>
|
||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="MinVersion">
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="MaxVersion">
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -57,7 +57,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
style="width: 170px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
style="width: 170px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
@@ -145,9 +145,9 @@
|
||||
<a-form-item label="xVer">
|
||||
<a-input-number v-model="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS" >
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
style="width: 135px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
style="width: 135px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -169,7 +169,7 @@
|
||||
<a-form-item label="Public Key">
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item >
|
||||
<a-form-item>
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -29,10 +29,12 @@
|
||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||
</template>
|
||||
<template slot="traffic" slot-scope="text, client">
|
||||
<a-tag color="blue">[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]</a-tag>
|
||||
<a-tag color="blue">
|
||||
[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]
|
||||
</a-tag>
|
||||
<template v-if="client._totalGB > 0">
|
||||
<a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]]GB</a-tag>
|
||||
<a-tag v-else color="cyan">[[client._totalGB]]GB</a-tag>
|
||||
<a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]] GB</a-tag>
|
||||
<a-tag v-else color="cyan">[[client._totalGB]] GB</a-tag>
|
||||
</template>
|
||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||
</template>
|
||||
@@ -42,7 +44,9 @@
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">[[ client._expiryTime ]] {{ i18n "pages.client.days" }}</a-tag>
|
||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">
|
||||
[[ client._expiryTime ]] {{ i18n "pages.client.days" }}
|
||||
</a-tag>
|
||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||
</template>
|
||||
{{end}}
|
||||
@@ -3,62 +3,66 @@
|
||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||
:closable="true"
|
||||
:mask-closable="true"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<table style="margin-bottom: 10px; width: 100%;">
|
||||
<tr><td>
|
||||
<table>
|
||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
||||
</table>
|
||||
</td>
|
||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||
</tr>
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||
|
||||
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
||||
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isQuic">
|
||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isKcp">
|
||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||
</template>
|
||||
</table>
|
||||
</td></tr>
|
||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||
<td v-if="inbound.tls">
|
||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.xtls">
|
||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.reality">
|
||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||
<tr>
|
||||
<td>
|
||||
<table>
|
||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
||||
</table>
|
||||
</td>
|
||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||
</tr>
|
||||
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||
|
||||
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
||||
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isQuic">
|
||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isKcp">
|
||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||
</template>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||
<td v-if="inbound.tls">
|
||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.xtls">
|
||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.reality">
|
||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else>
|
||||
tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -110,6 +114,7 @@
|
||||
<tr v-if="infoModal.clientSettings.subId">
|
||||
<td>Subscription link</td>
|
||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
||||
<td><a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', subBase + infoModal.clientSettings.subId)"></a-icon></td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.tgId">
|
||||
<td>Telegram ID</td>
|
||||
@@ -124,7 +129,8 @@
|
||||
<th>{{ i18n "encryption" }}</th>
|
||||
<th>{{ i18n "password" }}</th>
|
||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||
</tr><tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
@@ -136,7 +142,8 @@
|
||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||
<th>FollowRedirect</th>
|
||||
</tr><tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||
@@ -149,15 +156,18 @@
|
||||
<th>{{ i18n "password" }} Auth</th>
|
||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||
<th>IP</th>
|
||||
</tr><tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.settings.auth == 'password'">
|
||||
</tr>
|
||||
<tr v-if="inbound.settings.auth == 'password'">
|
||||
<td> </td>
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||
</tr>
|
||||
<tr v-for="account,index in inbound.settings.accounts">
|
||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
@@ -169,7 +179,8 @@
|
||||
<th> </th>
|
||||
<th>{{ i18n "username" }}</th>
|
||||
<th>{{ i18n "password" }}</th>
|
||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||
</tr>
|
||||
<tr v-for="account,index in inbound.settings.accounts">
|
||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||
@@ -180,10 +191,11 @@
|
||||
<div v-if="dbInbound.hasLink()">
|
||||
<a-divider>URL</a-divider>
|
||||
<p>[[ infoModal.link ]]</p>
|
||||
<button class="ant-btn ant-btn-primary" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
||||
<button class="ant-btn ant-btn-primary" id="copy-url-link" @click="copyToClipboard('copy-url-link', infoModal.link)"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
||||
</div>
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
const infoModal = {
|
||||
visible: false,
|
||||
inbound: new Inbound(),
|
||||
@@ -207,14 +219,6 @@
|
||||
this.isExpired = this.inbound.isExpiry(index);
|
||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||
this.visible = true;
|
||||
infoModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#copy-url-link', {
|
||||
text: () => this.link,
|
||||
});
|
||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
||||
}
|
||||
});
|
||||
},
|
||||
close() {
|
||||
infoModal.visible = false;
|
||||
@@ -233,42 +237,41 @@
|
||||
return this.infoModal.inbound;
|
||||
},
|
||||
get isActive() {
|
||||
if(infoModal.clientStats){
|
||||
if (infoModal.clientStats) {
|
||||
return infoModal.clientStats.enable;
|
||||
}
|
||||
return infoModal.dbInbound.isEnable;
|
||||
},
|
||||
get isEnable() {
|
||||
if(infoModal.clientSettings){
|
||||
if (infoModal.clientSettings) {
|
||||
return infoModal.clientSettings.enable;
|
||||
}
|
||||
return infoModal.dbInbound.isEnable;
|
||||
},
|
||||
get subBase() {
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port:"") + basePath + "sub/";
|
||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + basePath + "sub/";
|
||||
},
|
||||
get tgBase() {
|
||||
return "https://t.me/"
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyTextToClipboard(elmentId,content) {
|
||||
copyToClipboard(elmentId, content) {
|
||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||
text: () => content,
|
||||
});
|
||||
this.infoModal.clipboard.on('success', () => {
|
||||
text: () => content,
|
||||
});
|
||||
this.infoModal.clipboard.on('success', () => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
this.infoModal.clipboard.destroy();
|
||||
});
|
||||
},
|
||||
statsColor(stats) {
|
||||
if(!stats) return 'blue'
|
||||
if(stats['total'] === 0) return 'blue'
|
||||
else if(stats['total'] > 0 && (stats['down']+stats['up']) < stats['total']) return 'cyan'
|
||||
if (!stats) return 'blue'
|
||||
if (stats['total'] === 0) return 'blue'
|
||||
else if (stats['total'] > 0 && (stats['down'] + stats['up']) < stats['total']) return 'cyan'
|
||||
else return 'red'
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "inboundModal"}}
|
||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||
{{template "form/inbound"}}
|
||||
</a-modal>
|
||||
@@ -19,7 +19,7 @@
|
||||
ok() {
|
||||
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) {
|
||||
show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => {}, isEdit = false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
if (inbound) {
|
||||
@@ -44,7 +44,7 @@
|
||||
inModal.confirmLoading = loading;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clientSettings.vmesses;
|
||||
case Protocols.VLESS: return clientSettings.vlesses;
|
||||
case Protocols.TROJAN: return clientSettings.trojans;
|
||||
@@ -87,7 +87,7 @@
|
||||
get delayedExpireDays() {
|
||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||
},
|
||||
set delayedExpireDays(days){
|
||||
set delayedExpireDays(days) {
|
||||
this.client.expiryTime = -86400000 * days;
|
||||
},
|
||||
},
|
||||
@@ -100,15 +100,15 @@
|
||||
this.inModal.inbound.reality = false;
|
||||
}
|
||||
},
|
||||
setDefaultCertData(){
|
||||
setDefaultCertData() {
|
||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||
},
|
||||
setDefaultCertXtls(){
|
||||
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);
|
||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||
inModal.loading(false);
|
||||
@@ -117,15 +117,6 @@
|
||||
}
|
||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||
},
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
var len = 6 + Math.floor(Math.random() * 5);
|
||||
for(var ii=0; ii<len; ii++){
|
||||
string += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
client.email = string;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
{{ template "commonSider" . }}
|
||||
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||
<transition name="list" appear>
|
||||
@@ -24,7 +25,7 @@
|
||||
</a-tag>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable style="margin-bottom: 20px;" :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||
@@ -41,19 +42,19 @@
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "clients" }}:
|
||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
@@ -64,14 +65,14 @@
|
||||
</a-card>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
<div slot="title">
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
@@ -96,7 +97,7 @@
|
||||
style="width: 65px;"
|
||||
v-if="isRefreshEnabled"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||
@@ -115,7 +116,7 @@
|
||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="edit">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
@@ -174,19 +175,19 @@
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
<template v-if="clientCount[dbInbound.id]">
|
||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
@@ -244,6 +245,7 @@
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
<script>
|
||||
|
||||
const columns = [{
|
||||
@@ -315,7 +317,7 @@
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
siderDrawer,
|
||||
themeSwitcher,
|
||||
spinning: false,
|
||||
inbounds: [],
|
||||
dbInbounds: [],
|
||||
@@ -331,20 +333,22 @@
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning=true) {
|
||||
loading(spinning = true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
async getDBInbounds() {
|
||||
this.refreshing = true;
|
||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
||||
const msg = await HttpUtil.post('/panel/inbound/list');
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.setInbounds(msg.obj);
|
||||
this.refreshing = false;
|
||||
setTimeout(() => {
|
||||
this.refreshing = false;
|
||||
}, 500);
|
||||
},
|
||||
async getDefaultSettings() {
|
||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
@@ -361,29 +365,29 @@
|
||||
to_inbound = dbInbound.toInbound()
|
||||
this.inbounds.push(to_inbound);
|
||||
this.dbInbounds.push(dbInbound);
|
||||
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
||||
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
||||
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol)) {
|
||||
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
||||
}
|
||||
}
|
||||
this.searchInbounds(this.searchKey);
|
||||
},
|
||||
getClientCounts(dbInbound,inbound){
|
||||
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
||||
getClientCounts(dbInbound, inbound) {
|
||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
clientStats = dbInbound.clientStats
|
||||
now = new Date().getTime()
|
||||
if(clients){
|
||||
if (clients) {
|
||||
clientCount = clients.length;
|
||||
if(dbInbound.enable){
|
||||
if (dbInbound.enable) {
|
||||
clients.forEach(client => {
|
||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||
});
|
||||
clientStats.forEach(client => {
|
||||
if(!client.enable) {
|
||||
if (!client.enable) {
|
||||
depleted.push(client.email);
|
||||
} else {
|
||||
if ((client.expiryTime > 0 && (client.expiryTime-now < this.expireDiff)) ||
|
||||
(client.total > 0 && (client.total-(client.up+client.down) < this.trafficDiff ))) expiring.push(client.email);
|
||||
if ((client.expiryTime > 0 && (client.expiryTime - now < this.expireDiff)) ||
|
||||
(client.total > 0 && (client.total - (client.up + client.down) < this.trafficDiff))) expiring.push(client.email);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -409,10 +413,10 @@
|
||||
if (ObjectUtil.deepSearch(inbound, key)) {
|
||||
const newInbound = new DBInbound(inbound);
|
||||
const inboundSettings = JSON.parse(inbound.settings);
|
||||
if (inboundSettings.hasOwnProperty('clients')){
|
||||
if (inboundSettings.hasOwnProperty('clients')) {
|
||||
const searchedSettings = { "clients": [] };
|
||||
inboundSettings.clients.forEach(client => {
|
||||
if (ObjectUtil.deepSearch(client, key)){
|
||||
if (ObjectUtil.deepSearch(client, key)) {
|
||||
searchedSettings.clients.push(client);
|
||||
}
|
||||
});
|
||||
@@ -423,7 +427,7 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
generalActions(action){
|
||||
generalActions(action) {
|
||||
switch (action.key) {
|
||||
case "export":
|
||||
this.exportAllLinks();
|
||||
@@ -476,7 +480,7 @@
|
||||
break;
|
||||
}
|
||||
},
|
||||
openCloneInbound(dbInbound) {
|
||||
openCloneInbound(dbInbound) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||
@@ -491,7 +495,6 @@
|
||||
});
|
||||
},
|
||||
async cloneInbound(baseInbound, dbInbound) {
|
||||
const inbound = new Inbound();
|
||||
const data = {
|
||||
up: dbInbound.up,
|
||||
down: dbInbound.down,
|
||||
@@ -500,14 +503,14 @@
|
||||
enable: dbInbound.enable,
|
||||
expiryTime: dbInbound.expiryTime,
|
||||
|
||||
listen: inbound.listen,
|
||||
port: inbound.port,
|
||||
listen: '',
|
||||
port: RandomUtil.randomIntRange(10000, 60000),
|
||||
protocol: baseInbound.protocol,
|
||||
settings: inbound.settings.toString(),
|
||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||
streamSettings: baseInbound.stream.toString(),
|
||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||
};
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
await this.submit('/panel/inbound/add', data, inModal);
|
||||
},
|
||||
openAddInbound() {
|
||||
inModal.show({
|
||||
@@ -556,7 +559,7 @@
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
await this.submit('/panel/inbound/add', data, inModal);
|
||||
},
|
||||
async updateInbound(inbound, dbInbound) {
|
||||
const data = {
|
||||
@@ -575,7 +578,7 @@
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||
|
||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
},
|
||||
openAddClient(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -621,30 +624,30 @@
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
findIndexOfClient(clients,client) {
|
||||
findIndexOfClient(clients, client) {
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
},
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + clients.toString() +']}',
|
||||
settings: '{"clients": [' + clients.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/addClient`, data);
|
||||
await this.submit(`/panel/inbound/addClient`, data);
|
||||
},
|
||||
async updateClient(client, dbInboundId, clientId) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() +']}',
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data);
|
||||
},
|
||||
resetTraffic(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => {
|
||||
@@ -659,26 +662,26 @@
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/del/' + dbInboundId),
|
||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||
});
|
||||
},
|
||||
delClient(dbInboundId,client) {
|
||||
delClient(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clientId = this.getClientId(dbInbound.protocol,client);
|
||||
clientId = this.getClientId(dbInbound.protocol, client);
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||
});
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clientSettings.vmesses;
|
||||
case Protocols.VLESS: return clientSettings.vlesses;
|
||||
case Protocols.TROJAN: return clientSettings.trojans;
|
||||
@@ -687,22 +690,23 @@
|
||||
}
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch(protocol){
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
case Protocols.SHADOWSOCKS: return client.email;
|
||||
default: return client.id;
|
||||
}
|
||||
},
|
||||
showQrcode(dbInbound, clientIndex) {
|
||||
const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
|
||||
const link = dbInbound.genLink(clientIndex);
|
||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
|
||||
},
|
||||
showInfo(dbInbound, index) {
|
||||
infoModal.show(dbInbound, index);
|
||||
},
|
||||
switchEnable(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
||||
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
|
||||
},
|
||||
async switchEnableClient(dbInboundId, client) {
|
||||
this.loading()
|
||||
@@ -711,8 +715,8 @@
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol,clients[index]);
|
||||
await this.updateClient(clients[index],dbInboundId, clientId);
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data) {
|
||||
@@ -722,71 +726,71 @@
|
||||
}
|
||||
},
|
||||
getInboundClients(dbInbound) {
|
||||
if(dbInbound.protocol == Protocols.VLESS) {
|
||||
if (dbInbound.protocol == Protocols.VLESS) {
|
||||
return dbInbound.toInbound().settings.vlesses;
|
||||
} else if(dbInbound.protocol == Protocols.VMESS) {
|
||||
} else if (dbInbound.protocol == Protocols.VMESS) {
|
||||
return dbInbound.toInbound().settings.vmesses;
|
||||
} else if(dbInbound.protocol == Protocols.TROJAN) {
|
||||
} else if (dbInbound.protocol == Protocols.TROJAN) {
|
||||
return dbInbound.toInbound().settings.trojans;
|
||||
} else if(dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
||||
} else if (dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
||||
return dbInbound.toInbound().settings.shadowsockses;
|
||||
}
|
||||
},
|
||||
resetClientTraffic(client,dbInboundId) {
|
||||
resetClientTraffic(client, dbInboundId) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ client.email),
|
||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||
})
|
||||
},
|
||||
resetAllTraffic() {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/resetAllTraffics'),
|
||||
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
||||
});
|
||||
},
|
||||
resetAllClientTraffics(dbInboundId) {
|
||||
this.$confirm({
|
||||
title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||
content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||
})
|
||||
},
|
||||
delDepletedClients(dbInboundId) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
||||
})
|
||||
},
|
||||
isExpiry(dbInbound, index) {
|
||||
return dbInbound.toInbound().isExpiry(index)
|
||||
},
|
||||
getUpStats(dbInbound, email) {
|
||||
if(email.length == 0) return 0
|
||||
if (email.length == 0) return 0
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||
return clientStats ? clientStats.up : 0
|
||||
},
|
||||
getDownStats(dbInbound, email) {
|
||||
if(email.length == 0) return 0
|
||||
if (email.length == 0) return 0
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||
return clientStats ? clientStats.down : 0
|
||||
},
|
||||
isTrafficExhausted(dbInbound, email) {
|
||||
if(email.length == 0) return false
|
||||
if (email.length == 0) return false
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||
return clientStats ? clientStats.down + clientStats.up > clientStats.total : false
|
||||
},
|
||||
@@ -794,28 +798,28 @@
|
||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
||||
return clientStats ? clientStats['enable'] : true
|
||||
},
|
||||
isRemovable(dbInbound_id){
|
||||
isRemovable(dbInbound_id) {
|
||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
||||
},
|
||||
inboundLinks(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',dbInbound.genInboundLinks,dbInbound.remark);
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', dbInbound.genInboundLinks, dbInbound.remark);
|
||||
},
|
||||
exportAllLinks() {
|
||||
exportAllLinks() {
|
||||
let copyText = '';
|
||||
for (const dbInbound of this.dbInbounds) {
|
||||
copyText += dbInbound.genInboundLinks
|
||||
}
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
||||
},
|
||||
async startDataRefreshLoop() {
|
||||
while (this.isRefreshEnabled) {
|
||||
try {
|
||||
await this.getDBInbounds();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(this.refreshInterval);
|
||||
try {
|
||||
await this.getDBInbounds();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(this.refreshInterval);
|
||||
}
|
||||
},
|
||||
toggleRefresh() {
|
||||
@@ -824,11 +828,11 @@
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
},
|
||||
changeRefreshInterval(){
|
||||
changeRefreshInterval() {
|
||||
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||
},
|
||||
async manualRefresh(){
|
||||
if(!this.refreshing){
|
||||
async manualRefresh() {
|
||||
if (!this.refreshing) {
|
||||
this.spinning = true;
|
||||
await this.getDBInbounds();
|
||||
this.spinning = false;
|
||||
@@ -876,6 +880,7 @@
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{{template "inboundModal"}}
|
||||
@@ -885,5 +890,6 @@
|
||||
{{template "inboundInfoModal"}}
|
||||
{{template "clientsModal"}}
|
||||
{{template "clientsBulkModal"}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,32 +13,33 @@
|
||||
}
|
||||
|
||||
.ant-card-dark h2 {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
color: hsla(0, 0%, 100%, .65);
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<a-layout id="app" v-cloak>
|
||||
{{ template "commonSider" . }}
|
||||
<a-layout id="content-layout" :style="siderDrawer.isDarkTheme ? bgDarkStyle : ''">
|
||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||
<transition name="list" appear>
|
||||
<a-row>
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-row>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.cpu.color"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div>CPU</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.mem.color"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:percent="status.mem.percent"></a-progress>
|
||||
<div>
|
||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||
@@ -51,7 +52,7 @@
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.swap.color"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:percent="status.swap.percent"></a-progress>
|
||||
<div>
|
||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||
@@ -60,7 +61,7 @@
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.disk.color"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
:percent="status.disk.percent"></a-progress>
|
||||
<div>
|
||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||
@@ -75,14 +76,14 @@
|
||||
<transition name="list" appear>
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
<a-tooltip>
|
||||
@@ -94,7 +95,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<a-tooltip v-if="status.xray.state === State.Error">
|
||||
@@ -109,7 +110,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "menu.link" }}:
|
||||
<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">{{ i18n "pages.index.config" }}</a-tag>
|
||||
@@ -117,12 +118,12 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
@@ -133,7 +134,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
@@ -159,7 +160,7 @@
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="cloud-upload"></a-icon>
|
||||
@@ -191,7 +192,7 @@
|
||||
|
||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||
:closable="true" @ok="() => versionModal.visible = false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
footer="">
|
||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||
@@ -205,7 +206,7 @@
|
||||
|
||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||
:class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:class="themeSwitcher.darkCardClass"
|
||||
width="800px"
|
||||
footer="">
|
||||
<a-form layout="inline">
|
||||
@@ -213,7 +214,7 @@
|
||||
<a-select v-model="logModal.rows"
|
||||
style="width: 80px"
|
||||
@change="openLogs(logModal.rows)"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
<a-select-option value="50">50</a-select-option>
|
||||
@@ -235,13 +236,13 @@
|
||||
</a-modal>
|
||||
|
||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||
:closable="true" :class="siderDrawer.isDarkTheme ? darkClass : ''"
|
||||
:closable="true" :class="themeSwitcher.darkCardClass"
|
||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
||||
[[ backupModal.description ]]
|
||||
</p>
|
||||
<a-space direction="horizontal" align="center" style="margin-bottom: 10px;">
|
||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||
<a-button type="primary" @click="exportDatabase()">
|
||||
[[ backupModal.exportText ]]
|
||||
</a-button>
|
||||
@@ -253,6 +254,7 @@
|
||||
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
|
||||
@@ -295,13 +297,13 @@
|
||||
this.disk = new CurTotal(0, 0);
|
||||
this.loads = [0, 0, 0];
|
||||
this.mem = new CurTotal(0, 0);
|
||||
this.netIO = {up: 0, down: 0};
|
||||
this.netTraffic = {sent: 0, recv: 0};
|
||||
this.netIO = { up: 0, down: 0 };
|
||||
this.netTraffic = { sent: 0, recv: 0 };
|
||||
this.swap = new CurTotal(0, 0);
|
||||
this.tcpCount = 0;
|
||||
this.udpCount = 0;
|
||||
this.uptime = 0;
|
||||
this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
|
||||
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
@@ -386,7 +388,7 @@
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
data: {
|
||||
siderDrawer,
|
||||
themeSwitcher,
|
||||
status: new Status(),
|
||||
versionModal,
|
||||
logModal,
|
||||
@@ -422,7 +424,7 @@
|
||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||
okText: '{{ i18n "confirm"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
class: themeSwitcher.darkCardClass,
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: async () => {
|
||||
versionModal.hide();
|
||||
@@ -448,9 +450,9 @@
|
||||
return;
|
||||
}
|
||||
},
|
||||
async openLogs(rows){
|
||||
async openLogs(rows) {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/logs/'+rows);
|
||||
const msg = await HttpUtil.post('server/logs/' + rows);
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
@@ -498,7 +500,7 @@
|
||||
return;
|
||||
}
|
||||
this.loading(true);
|
||||
const restartMsg = await HttpUtil.post("/xui/setting/restartPanel");
|
||||
const restartMsg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||
this.loading(false);
|
||||
if (restartMsg.success) {
|
||||
this.loading(true);
|
||||
|
||||
@@ -179,7 +179,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
}
|
||||
|
||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
url := "https://api.github.com/repos/mhsanaei/Xray-core/releases"
|
||||
url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -246,7 +246,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||
url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
||||
url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
||||
continue
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.SubID == subId {
|
||||
if client.Enable && client.SubID == subId {
|
||||
link := s.getLink(inbound, client.Email)
|
||||
result = append(result, link)
|
||||
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||
@@ -73,7 +73,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -381,6 +381,9 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
||||
}
|
||||
|
||||
func (t *Tgbot) SendMsgToTgbot(tgid int64, msg string, inlineKeyboard ...tgbotapi.InlineKeyboardMarkup) {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
var allMessages []string
|
||||
limit := 2000
|
||||
// paging message if it is big
|
||||
|
||||
@@ -150,8 +150,6 @@
|
||||
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
||||
"resetAllTrafficOkText" = "Confirm"
|
||||
"resetAllTrafficCancelText" = "Cancel"
|
||||
"IPLimit" = "IP Limit"
|
||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
||||
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
||||
@@ -161,8 +159,10 @@
|
||||
"delDepletedClients" = "Delete Depleted Clients"
|
||||
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||
"delDepletedClientsContent" = "Are you sure you want to delete all depleted clients?"
|
||||
"Email" = "Email"
|
||||
"EmailDesc" = "Please provide a unique email address."
|
||||
"email" = "Email"
|
||||
"emailDesc" = "Please provide a unique email address."
|
||||
"IPLimit" = "IP Limit"
|
||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (enter 0 to disable IP limit)."
|
||||
"IPLimitlog" = "IP 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"
|
||||
@@ -170,7 +170,7 @@
|
||||
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
||||
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
||||
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )"
|
||||
"subscriptionDesc" = "you can find your sub link on Details, also ou can use the same name for several configurations"
|
||||
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
||||
|
||||
[pages.client]
|
||||
"add" = "Add Client"
|
||||
@@ -277,6 +277,8 @@
|
||||
"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."
|
||||
"xrayConfigSpeedtest" = "Block Speedtest Websites"
|
||||
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest 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"
|
||||
|
||||
@@ -157,10 +157,10 @@
|
||||
"delDepletedClients" = "حذف کاربران منقضی"
|
||||
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||
"email" = "ایمیل"
|
||||
"emailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||
"IPLimit" = "محدودیت ای پی"
|
||||
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||
"Email" = "ایمیل"
|
||||
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||
"IPLimitlog" = "گزارش ها"
|
||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||
@@ -275,6 +275,8 @@
|
||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
||||
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
|
||||
"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||
|
||||
328
web/translation/translate.ru_RU.toml
Normal file
@@ -0,0 +1,328 @@
|
||||
"username" = "имя пользователя"
|
||||
"password" = "пароль"
|
||||
"login" = "логин"
|
||||
"confirm" = "подтвердить"
|
||||
"cancel" = "отмена"
|
||||
"close" = "закрыть"
|
||||
"copy" = "копировать"
|
||||
"copied" = "скопировано"
|
||||
"download" = "скачать"
|
||||
"remark" = "примечание"
|
||||
"enable" = "включить"
|
||||
"protocol" = "протокол"
|
||||
"search" = "поиск"
|
||||
|
||||
"loading" = "загрузка"
|
||||
"second" = "секунда"
|
||||
"minute" = "минута"
|
||||
"hour" = "час"
|
||||
"day" = "день"
|
||||
"check" = "просмотр"
|
||||
"indefinite" = "бессрочно"
|
||||
"unlimited" = "безлимитно"
|
||||
"none" = "пусто"
|
||||
"qrCode" = "QR-код"
|
||||
"info" = "больше информации"
|
||||
"edit" = "изменить"
|
||||
"delete" = "удалить"
|
||||
"reset" = "обнулить"
|
||||
"copySuccess" = "скопировано"
|
||||
"sure" = "да"
|
||||
"encryption" = "Шифрование"
|
||||
"transmission" = "протокол передачи"
|
||||
"host" = "хост"
|
||||
"path" = "путь"
|
||||
"camouflage" = "маскировка"
|
||||
"status" = "статус"
|
||||
"enabled" = "включено"
|
||||
"disabled" = "отключено"
|
||||
"depleted" = "исчерпано"
|
||||
"depletingSoon" = "почти исчерпано"
|
||||
"domainName" = "домен"
|
||||
"additional" = "допольнительно"
|
||||
"monitor" = "порт IP"
|
||||
"certificate" = "сертификат"
|
||||
"fail" = "неудача"
|
||||
"success" = "успешно"
|
||||
"getVersion" = "узнать версию"
|
||||
"install" = "установка"
|
||||
"clients" = "клиенты"
|
||||
"usage" = "использование"
|
||||
"secretToken" = "секретный токен"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "статус системы"
|
||||
"inbounds" = "пользователи"
|
||||
"settings" = "настройки"
|
||||
"logout" = "выход"
|
||||
"link" = "другое"
|
||||
|
||||
[pages.login]
|
||||
"title" = "логин"
|
||||
"loginAgain" = "Время пребывания в сети вышло. Пожалуйста, войдите в систему снова."
|
||||
|
||||
[pages.login.toasts]
|
||||
"invalidFormData" = "Недопустимый формат данных."
|
||||
"emptyUsername" = "Введите имя пользователя."
|
||||
"emptyPassword" = "Введите пароль."
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя или пароль."
|
||||
"successLogin" = "успешный вход"
|
||||
|
||||
[pages.index]
|
||||
"title" = "статус системы"
|
||||
"memory" = "память"
|
||||
"hard" = "жесткий диск"
|
||||
"xrayStatus" = "статус Xray"
|
||||
"stopXray" = "стоп"
|
||||
"restartXray" = "рестарт Xray"
|
||||
"xraySwitch" = "переключить версию"
|
||||
"xraySwitchClick" = "Выберите желаемую версию."
|
||||
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями."
|
||||
"operationHours" = "Часы работы"
|
||||
"operationHoursDesc" = "Аптайм системы: время системы в сети."
|
||||
"systemLoad" = "Системная нагрузка"
|
||||
"connectionCount" = "количество соединений"
|
||||
"connectionCountDesc" = "Всего подключений по всем сетям»."
|
||||
"upSpeed" = "Общая скорость upload."
|
||||
"downSpeed" = "Общая скорость download."
|
||||
"totalSent" = "Общий объем загруженных данных с момента запуска системы"
|
||||
"totalReceive" = "Общий объем полученных данных с момента запуска системы.."
|
||||
"xraySwitchVersionDialog" = "переключить версию Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
||||
"dontRefresh" = "Установка. Не обновляйте эту страницу."
|
||||
"logs" = "Логи"
|
||||
"config" = "Конфиг"
|
||||
"backup" = "Бекап и восстановление"
|
||||
"backupTitle" = "База данных бекапа и восстановления"
|
||||
"backupDescription" = "Не забудьте сделать резервную копию перед импортом новой базы данных."
|
||||
"exportDatabase" = "Экспорт базы данных"
|
||||
"importDatabase" = "Импорт базы данных"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "пользователи"
|
||||
"totalDownUp" = "Всего входящих/исходящих"
|
||||
"totalUsage" = "Всего использовано"
|
||||
"inboundCount" = "Количество пользователей"
|
||||
"operate" = "Меню"
|
||||
"enable" = "Включить"
|
||||
"remark" = "Примечание"
|
||||
"protocol" = "Протокол"
|
||||
"port" = "Порт"
|
||||
"traffic" = "Траффик"
|
||||
"details" = "Подробнее"
|
||||
"transportConfig" = "Перенести"
|
||||
"expireDate" = "Дата окончания"
|
||||
"resetTraffic" = "Обнулить траффик"
|
||||
"addInbound" = "Добавить пользователя"
|
||||
"generalActions" = "Общие действия"
|
||||
"create" = "Создать"
|
||||
"update" = "Обновить"
|
||||
"modifyInbound" = "Изменить данные"
|
||||
"deleteInbound" = "Удалить пользователя"
|
||||
"deleteInboundContent" = "Подтвердите удаление пользователя?"
|
||||
"resetTrafficContent" = "Подтвердите обнуление траффика?"
|
||||
"copyLink" = "Копировать ключ"
|
||||
"address" = "Адрес"
|
||||
"network" = "Сеть"
|
||||
"destinationPort" = "Порт назначения"
|
||||
"targetAddress" = "Целевой адрес"
|
||||
"disableInsecureEncryption" = "Отключить небезопасное шифрование"
|
||||
"monitorDesc" = "Оставьте пустым по умолчанию"
|
||||
"meansNoLimit" = "Значит без ограничений"
|
||||
"totalFlow" = "Общий расход"
|
||||
"leaveBlankToNeverExpire" = "Оставьте пустым = бессрочно"
|
||||
"noRecommendKeepDefault" = "Нет требований для сохранения настроек по умолчанию"
|
||||
"certificatePath" = "Путь файла сертификата"
|
||||
"certificateContent" = "Содержимое файла сертификата"
|
||||
"publicKeyPath" = "Путь к публичному ключу"
|
||||
"publicKeyContent" = "Содержимое публичного ключа"
|
||||
"keyPath" = "Путь к приватному ключу"
|
||||
"keyContent" = "Содержимое приватного ключа"
|
||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||
"client" = "Клиент"
|
||||
"export" = "Поделиться ключом"
|
||||
"clone" = "Клонировать"
|
||||
"cloneInbound" = "Клонировать пользователя"
|
||||
"cloneInboundContent" = "Все настройки этого пользователя, кроме порта, порт прослушки и клиентов, будут клонированы."
|
||||
"cloneInboundOk" = "Клонировано"
|
||||
"resetAllTraffic" = "Обнулить весь траффик"
|
||||
"resetAllTrafficTitle" = "Обнуление всего траффика"
|
||||
"resetAllTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
||||
"resetAllTrafficOkText" = "Подтвердить"
|
||||
"resetAllTrafficCancelText" = "Отмена"
|
||||
"resetInboundClientTraffics" = "Обнулить траффик пользователей"
|
||||
"resetInboundClientTrafficTitle" = "Обнуление траффика пользователей"
|
||||
"resetInboundClientTrafficContent" = "Вы уверены, что хотите обнулить весь трафик для этих пользователей?"
|
||||
"resetAllClientTraffics" = "Обнулить весь траффик пользователей"
|
||||
"resetAllClientTrafficTitle" = "Обнуление всего траффика пользователей"
|
||||
"resetAllClientTrafficContent" = "Подтверждаете обнуление всего траффика пользователей?"
|
||||
"delDepletedClients" = "Удалить отключенных пользователей"
|
||||
"delDepletedClientsTitle" = "Удаление отключенных пользователей"
|
||||
"delDepletedClientsContent" = "Подтверждаете удаление отключенных пользователей?"
|
||||
"email" = "Email"
|
||||
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||
"IPLimit" = "ограничение по IP"
|
||||
"IPLimitDesc" = "Отключить ключ, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)."
|
||||
"IPLimitlog" = "IP лог"
|
||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)."
|
||||
"IPLimitlogclear" = "Очистить список"
|
||||
"setDefaultCert" = "Установить сертификат с панели"
|
||||
"xtlsDesc" = "Версия Xray должна быть не ниже 1.7.5"
|
||||
"realityDesc" = "Версия Xray должна быть 1.8.0 или выше."
|
||||
"telegramDesc" = "используйте Telegram ID (вы можете получить его у @userinfobot)"
|
||||
"subscriptionDesc" = "вы можете найти свою ссылку подписки в разделе «Подробнее», также вы можете использовать одно и то же имя для нескольких конфигов."
|
||||
|
||||
[pages.client]
|
||||
"add" = "Добавить пользователя"
|
||||
"edit" = "Редактировать пользователя"
|
||||
"submitAdd" = "Добавить пользователя"
|
||||
"submitEdit" = "Сохранить изменения"
|
||||
"clientCount" = "Количество пользователей"
|
||||
"bulk" = "Добавить несколько"
|
||||
"method" = "Метод"
|
||||
"first" = "Первый"
|
||||
"last" = "Последний"
|
||||
"prefix" = "Префикс"
|
||||
"postfix" = "Постфикс"
|
||||
"delayedStart" = "Начать со времени первого подключения"
|
||||
"expireDays" = "Срок действия"
|
||||
"days" = "дней"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "Получить"
|
||||
|
||||
[pages.inbounds.stream.general]
|
||||
"requestHeader" = "Требуется заголовок"
|
||||
"name" = "Имя"
|
||||
"value" = "Значение"
|
||||
|
||||
[pages.inbounds.stream.tcp]
|
||||
"requestVersion" = "Требуется версия"
|
||||
"requestMethod" = "Требуется метод"
|
||||
"requestPath" = "Требуется путь"
|
||||
"responseVersion" = "Указать версию"
|
||||
"responseStatus" = "Указать статус"
|
||||
"responseStatusDescription" = "Указать примечание статуса"
|
||||
"responseHeader" = "Указать заголовок"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Шифрование"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "Настройки"
|
||||
"save" = "Сохранить"
|
||||
"restartPanel" = "Рестарт панели"
|
||||
"restartPanelDesc" = "Подтвердите рестарт панели? ОК для рестарта панели через 3 сек. Если вы не можете пользоваться панелью после рестарта, пожалуйста, посмотрите лог панели на сервере"
|
||||
"actions" = "Действия"
|
||||
"resetDefaultConfig" = "Сбросить всё по-умолчанию"
|
||||
"panelSettings" = "Настройки панели"
|
||||
"securitySettings" = "Настройки безопасности"
|
||||
"xrayConfiguration" = "Конфигурация Xray"
|
||||
"TGBotSettings" = "Настройки Телеграм-бота"
|
||||
"panelListeningIP" = "IP-порт панели"
|
||||
"panelListeningIPDesc" = "Оставьте пустым для работы с любого IP. Перезагрузите панель для применения настроек."
|
||||
"panelPort" = "Порт панели"
|
||||
"panelPortDesc" = "Перезагрузите панель для применения настроек."
|
||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек."
|
||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с «/». Перезагрузите панель для применения настроек."
|
||||
"panelUrlPath" = "Корневой путь URL-адреса панели"
|
||||
"panelUrlPathDesc" = "Должен начинаться с «/» и заканчиваться на «/». Перезагрузите панель для применения настроек."
|
||||
"oldUsername" = "Имя пользователя сейчас"
|
||||
"currentPassword" = "Пароль сейчас"
|
||||
"newUsername" = "Новое имя пользователя"
|
||||
"newPassword" = "Новый пароль"
|
||||
"telegramBotEnable" = "Включить Телеграм-бота"
|
||||
"telegramBotEnableDesc" = "Перезагрузите панель для применения настроек."
|
||||
"telegramToken" = "Токен Телеграм-бота"
|
||||
"telegramTokenDesc" = "Перезагрузите панель для применения настроек."
|
||||
"telegramChatId" = "Телеграм-ID админа бота"
|
||||
"telegramChatIdDesc" = "Если несколько Телеграм-ID, разделить запятой. Используйте @userinfobot, чтобы получить Телеграм-ID. Перезагрузите панель для применения настроек."
|
||||
"telegramNotifyTime" = "Частота уведомлений телеграм-бота"
|
||||
"telegramNotifyTimeDesc" = "Используйте формат Crontab. Перезагрузите панель для применения настроек."
|
||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете. Перезагрузите панель для применения настроек."
|
||||
"sessionMaxAge" = "Продолжительность сессии"
|
||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
||||
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
||||
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
|
||||
"trafficDiff" = "Порог траффика для уведомления"
|
||||
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
|
||||
"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления"
|
||||
"tgNotifyCpuDesc" = "Получение уведомления, если нагрузка на ЦП превышает этот порог (значение:%)"
|
||||
"timeZone" = "Временная зона"
|
||||
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе. Перезагрузите панель для применения настроек."
|
||||
|
||||
[pages.settings.templates]
|
||||
"title" = "Шаблоны"
|
||||
"basicTemplate" = "Базовые шаблоны"
|
||||
"advancedTemplate" = "Расширенные шаблоны"
|
||||
"completeTemplate" = "Конфигурация шаблона"
|
||||
"generalConfigs" = "Основные настройки"
|
||||
"generalConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
||||
"countryConfigs" = "Настройки по гео"
|
||||
"countryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
||||
"ipv4Configs" = "Настройки IPv4 "
|
||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4."
|
||||
"warpConfigs" = "Настройки WARP"
|
||||
"warpConfigsDesc" = "Внимание: перед использованием этих параметров установите WARP в режиме прокси-сервера socks5 на свой сервер, следуя инструкциям на GitHub панели. WARP будет направлять трафик на веб-сайты через серверы Cloudflare."
|
||||
"xrayConfigTemplate" = "Шаблон конфигурации Xray"
|
||||
"xrayConfigTemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigTorrent" = "Запретить использование BitTorrent"
|
||||
"xrayConfigTorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigPrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
||||
"xrayConfigPrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigAds" = "Бокировка рекламы"
|
||||
"xrayConfigAdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigPorn" = "Блокировка порносайтов"
|
||||
"xrayConfigPornDesc" = "Измените конфигурацию, чтобы отключить подключения к порносайтам. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigSpeedtest" = "Блокировать сайты для проверки скорости"
|
||||
"xrayConfigSpeedtestDesc" = "Измените шаблон конфигурации, чтобы избежать подключения к веб-сайтам для тестирования скорости. Перезапустите панель, чтобы применить изменения."
|
||||
"xrayConfigIRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||
"xrayConfigIRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigIRDomain" = "Отключить подключение к доменам Ирана"
|
||||
"xrayConfigIRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
||||
"xrayConfigChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigChinaDomain" = "Отключить подключение к доменам Китая"
|
||||
"xrayConfigChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigRussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
||||
"xrayConfigRussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigRussiaDomain" = "Отключить подключение к доменам России"
|
||||
"xrayConfigRussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigGoogleIPv4" = "Использовать IPv4 для Google"
|
||||
"xrayConfigGoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigNetflixIPv4" = "Использовать IPv4 для Netflix"
|
||||
"xrayConfigNetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigGoogleWARP" = "Маршрутизация Google через WARP."
|
||||
"xrayConfigGoogleWARPDesc" = "Применить маршрутизацию Google через WARP. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigOpenAIWARP" = "Маршрут OpenAI (ChatGPT) через WARP."
|
||||
"xrayConfigOpenAIWARPDesc" = "Применить маршрутизацию для OpenAI (ChatGPT) через WARP. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigNetflixWARP" = "Маршрутизация Netflix через WARP."
|
||||
"xrayConfigNetflixWARPDesc" = "Применить маршрутизацию Netflix через WARP. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigSpotifyWARP" = "Маршрутизация Spotify через WARP."
|
||||
"xrayConfigSpotifyWARPDesc" = "Применить маршрутизацию Spotify через WARP. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigIRWARP" = "Маршрутизация доменов Ирана через WARP."
|
||||
"xrayConfigIRWARPDesc" = "Применить маршрутизацию для доменов Ирана через WARP. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigInbounds" = "Конфигурация подключений"
|
||||
"xrayConfigInboundsDesc" = "Изменение шаблона конфигурации, для подключения определенных пользователей. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigOutbounds" = "Конфигурация исходящих"
|
||||
"xrayConfigOutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера. Перезагрузите панель для применения настроек."
|
||||
"xrayConfigRoutings" = "Настройка правил маршрутизации."
|
||||
"xrayConfigRoutingsDesc" = "Изменение шаблона конфигурации, для определения правил маршрутизации для этого сервера. Перезагрузите панель для применения настроек."
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Админ"
|
||||
"secret" = "Секретный токен"
|
||||
"loginSecurity" = "Безопасность входа"
|
||||
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
|
||||
"secretToken" = "Секретный токен"
|
||||
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью командного инструмента x-ui."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Изменение настроек"
|
||||
"getSettings" = "Просмотр настроек"
|
||||
"modifyUser" = "Изменение пользователя "
|
||||
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
||||
"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены"
|
||||
@@ -157,10 +157,10 @@
|
||||
"delDepletedClients" = "删除耗尽的客户端"
|
||||
"delDepletedClientsTitle" = "删除耗尽的客户"
|
||||
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
||||
"email" = "电子邮件"
|
||||
"emailDesc" = "电子邮件必须完全唯"
|
||||
"IPLimit" = "IP限制"
|
||||
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
||||
"Email" = "电子邮件"
|
||||
"EmailDesc" = "电子邮件必须完全唯"
|
||||
"IPLimitlog" = "IP日志"
|
||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||
"IPLimitlogclear" = "清除日志"
|
||||
@@ -275,6 +275,8 @@
|
||||
"xrayConfigAdsDesc" = "修改配置模板屏蔽广告,重启面板生效"
|
||||
"xrayConfigPorn" = "禁止色情网站连接"
|
||||
"xrayConfigPornDesc" = "更改配置模板避免连接色情网站,重启面板生效"
|
||||
"xrayConfigSpeedtest" = "阻止测速网站"
|
||||
"xrayConfigSpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。"
|
||||
"xrayConfigIRIp" = "禁止伊朗 IP 范围连接"
|
||||
"xrayConfigIRIpDesc" = "修改配置模板避免连接伊朗IP段,重启面板生效"
|
||||
"xrayConfigIRDomain" = "禁止伊朗域连接"
|
||||
|
||||
@@ -83,7 +83,7 @@ type Server struct {
|
||||
|
||||
index *controller.IndexController
|
||||
server *controller.ServerController
|
||||
xui *controller.XUIController
|
||||
panel *controller.XUIController
|
||||
api *controller.APIController
|
||||
sub *controller.SUBController
|
||||
|
||||
@@ -207,7 +207,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
|
||||
s.index = controller.NewIndexController(g)
|
||||
s.server = controller.NewServerController(g)
|
||||
s.xui = controller.NewXUIController(g)
|
||||
s.panel = controller.NewXUIController(g)
|
||||
s.api = controller.NewAPIController(g)
|
||||
s.sub = controller.NewSUBController(g)
|
||||
|
||||
|
||||
87
x-ui.sh
@@ -42,7 +42,7 @@ if [[ "${release}" == "centos" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 20 ]]; then
|
||||
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
|
||||
fi
|
||||
@@ -432,8 +432,7 @@ show_xray_status() {
|
||||
}
|
||||
|
||||
open_ports() {
|
||||
if ! command -v ufw &> /dev/null
|
||||
then
|
||||
if ! command -v ufw &>/dev/null; then
|
||||
echo "ufw firewall is not installed. Installing now..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ufw
|
||||
@@ -460,22 +459,23 @@ open_ports() {
|
||||
|
||||
# Check if the input is valid
|
||||
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
|
||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2; exit 1
|
||||
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Open the specified ports using ufw
|
||||
IFS=',' read -ra PORT_LIST <<< "$ports"
|
||||
IFS=',' read -ra PORT_LIST <<<"$ports"
|
||||
for port in "${PORT_LIST[@]}"; do
|
||||
if [[ $port == *-* ]]; then
|
||||
# Split the range into start and end ports
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Loop through the range and open each port
|
||||
for ((i=start_port; i<=end_port; i++)); do
|
||||
sudo ufw allow $i
|
||||
done
|
||||
# Split the range into start and end ports
|
||||
start_port=$(echo $port | cut -d'-' -f1)
|
||||
end_port=$(echo $port | cut -d'-' -f2)
|
||||
# Loop through the range and open each port
|
||||
for ((i = start_port; i <= end_port; i++)); do
|
||||
sudo ufw allow $i
|
||||
done
|
||||
else
|
||||
sudo ufw allow "$port"
|
||||
sudo ufw allow "$port"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -529,7 +529,7 @@ ssl_cert_issue() {
|
||||
fi
|
||||
fi
|
||||
#install socat second
|
||||
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
|
||||
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]]; then
|
||||
yum install socat -y
|
||||
else
|
||||
apt install socat -y
|
||||
@@ -555,16 +555,16 @@ ssl_cert_issue() {
|
||||
else
|
||||
LOGI "your domain is ready for issuing cert now..."
|
||||
fi
|
||||
|
||||
#create a directory for install cert
|
||||
certPath="/root/cert/${domain}"
|
||||
if [ ! -d "$certPath" ]; then
|
||||
mkdir -p "$certPath"
|
||||
else
|
||||
rm -rf "$certPath"
|
||||
mkdir -p "$certPath"
|
||||
fi
|
||||
|
||||
|
||||
#create a directory for install cert
|
||||
certPath="/root/cert/${domain}"
|
||||
if [ ! -d "$certPath" ]; then
|
||||
mkdir -p "$certPath"
|
||||
else
|
||||
rm -rf "$certPath"
|
||||
mkdir -p "$certPath"
|
||||
fi
|
||||
|
||||
#get needed port here
|
||||
local WebPort=80
|
||||
read -p "please choose which port do you use,default will be 80 port:" WebPort
|
||||
@@ -595,22 +595,21 @@ ssl_cert_issue() {
|
||||
else
|
||||
LOGI "install certs succeed,enable auto renew..."
|
||||
fi
|
||||
|
||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "auto renew failed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
exit 1
|
||||
else
|
||||
LOGI "auto renew succeed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
fi
|
||||
|
||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "auto renew failed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
exit 1
|
||||
else
|
||||
LOGI "auto renew succeed, certs details:"
|
||||
ls -lah cert/*
|
||||
chmod 755 $certPath/*
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
warp_fixchatgpt() {
|
||||
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
||||
echo ""
|
||||
@@ -619,21 +618,21 @@ warp_fixchatgpt() {
|
||||
|
||||
run_speedtest() {
|
||||
# Check if Speedtest is already installed
|
||||
if ! command -v speedtest &> /dev/null; then
|
||||
if ! command -v speedtest &>/dev/null; then
|
||||
# If not installed, install it
|
||||
if command -v dnf &> /dev/null; then
|
||||
if command -v dnf &>/dev/null; then
|
||||
sudo dnf install -y curl
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
|
||||
sudo dnf install -y speedtest
|
||||
elif command -v yum &> /dev/null; then
|
||||
elif command -v yum &>/dev/null; then
|
||||
sudo yum install -y curl
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh | sudo bash
|
||||
sudo yum install -y speedtest
|
||||
elif command -v apt-get &> /dev/null; then
|
||||
elif command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y curl
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash
|
||||
sudo apt-get install -y speedtest
|
||||
elif command -v apt &> /dev/null; then
|
||||
elif command -v apt &>/dev/null; then
|
||||
sudo apt update && sudo apt install -y curl
|
||||
curl -s https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh | sudo bash
|
||||
sudo apt install -y speedtest
|
||||
@@ -647,8 +646,6 @@ run_speedtest() {
|
||||
speedtest
|
||||
}
|
||||
|
||||
|
||||
|
||||
show_usage() {
|
||||
echo "x-ui control menu usages: "
|
||||
echo "------------------------------------------"
|
||||
@@ -760,7 +757,7 @@ show_menu() {
|
||||
19)
|
||||
warp_fixchatgpt
|
||||
;;
|
||||
20)
|
||||
20)
|
||||
run_speedtest
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type InboundConfig struct {
|
||||
Listen json_util.RawMessage `json:"listen"` // listen 不能为空字符串
|
||||
Listen json_util.RawMessage `json:"listen"` // listen cannot be an empty string
|
||||
Port int `json:"port"`
|
||||
Protocol string `json:"protocol"`
|
||||
Settings json_util.RawMessage `json:"settings"`
|
||||
|
||||