Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1700e85d06 | ||
|
|
931e949442 | ||
|
|
8bca416142 | ||
|
|
adf2b96f38 | ||
|
|
e47ea983bd | ||
|
|
b62f747e88 | ||
|
|
784e6e24e2 | ||
|
|
4af4aadd14 | ||
|
|
9b3415865a | ||
|
|
8b6ea9e0d5 | ||
|
|
2a6f678b53 | ||
|
|
7225f00c2b | ||
|
|
85db586d95 | ||
|
|
2d7663f971 | ||
|
|
c935014ba9 | ||
|
|
2c61b5a426 | ||
|
|
a5cfe4fc1e | ||
|
|
3cc3b3b04d | ||
|
|
8e550bc308 | ||
|
|
3498dcd50d | ||
|
|
abb8a7084e | ||
|
|
e2be6ce851 | ||
|
|
f30235373a | ||
|
|
9d75925f30 | ||
|
|
fbc8fa7345 | ||
|
|
d971a61afc | ||
|
|
971e8f2351 | ||
|
|
a9d29a4dfc | ||
|
|
6639d69a6d | ||
|
|
8114e3fc17 |
57
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,57 +0,0 @@
|
|||||||
name: Issue Report
|
|
||||||
description: "Create a report to help us improve."
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Welcome
|
|
||||||
options:
|
|
||||||
- label: Yes, I'm using the latest major release. Only such installations are supported.
|
|
||||||
required: true
|
|
||||||
- label: Yes, I'm using the supported system. Only such systems are supported.
|
|
||||||
required: true
|
|
||||||
- label: Yes, I have read all WIKI document,nothing can help me in my problem.
|
|
||||||
required: true
|
|
||||||
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
|
||||||
required: true
|
|
||||||
- label: Yes, I've included all information below (version, config, log, etc).
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: Description of the problem,screencshot would be good
|
|
||||||
placeholder: Your problem description
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Version of x-ui
|
|
||||||
value: |-
|
|
||||||
<details>
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ x-ui version
|
|
||||||
# Paste output here
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: x-ui log or xray log
|
|
||||||
value: |-
|
|
||||||
<details>
|
|
||||||
|
|
||||||
```console
|
|
||||||
# paste log here
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
66
.github/workflows/docker.yml
vendored
@@ -1,18 +1,12 @@
|
|||||||
name: Docker Image CI
|
name: Docker Image CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
tags:
|
||||||
|
- "*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
project:
|
|
||||||
description: 'Project'
|
|
||||||
required: true
|
|
||||||
default:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -20,30 +14,42 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
alireza7/x-ui
|
||||||
|
ghcr.io/alireza0/x-ui
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=tag
|
||||||
|
type=pep440,pattern={{version}}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
|
||||||
- name: Docker Hub login
|
|
||||||
env:
|
|
||||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
echo "${DOCKERHUB_TOKEN}" | docker login --username ${DOCKERHUB_USERNAME} --password-stdin
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
uses: docker/setup-buildx-action@v2
|
||||||
uses: crazy-max/ghaction-docker-buildx@v1
|
|
||||||
with:
|
|
||||||
buildx-version: latest
|
|
||||||
|
|
||||||
- name: Build Dockerfile
|
- name: Login to Docker Hub
|
||||||
env:
|
uses: docker/login-action@v2
|
||||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
|
with:
|
||||||
DOCKERHUB_REPO: ${{ secrets.DOCKER_HUB_REPOSITORY }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
run: |
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
docker buildx build \
|
|
||||||
--platform=linux/amd64,linux/arm64 \
|
- name: Login to GHCR
|
||||||
--output "type=image,push=true" \
|
uses: docker/login-action@v2
|
||||||
--file ./Dockerfile ./ \
|
with:
|
||||||
--tag $(echo "${DOCKERHUB_USERNAME}" | tr '[:upper:]' '[:lower:]')/${{ github.event.inputs.project }}:latest
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
21
.github/workflows/release.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
@@ -14,7 +15,7 @@ jobs:
|
|||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GAYHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
@@ -29,7 +30,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: '1.20'
|
||||||
- name: build linux amd64 version
|
- name: build linux amd64 version
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||||
@@ -41,7 +42,7 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/hossinasaadi/Xray-core/releases/latest/download/Xray-linux-64.zip
|
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -54,7 +55,7 @@ jobs:
|
|||||||
- name: upload
|
- name: upload
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GAYHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||||
asset_path: x-ui-linux-amd64.tar.gz
|
asset_path: x-ui-linux-amd64.tar.gz
|
||||||
@@ -69,7 +70,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: '1.20'
|
||||||
- name: build linux arm64 version
|
- name: build linux arm64 version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -83,7 +84,7 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/hossinasaadi/Xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -96,7 +97,7 @@ jobs:
|
|||||||
- name: upload
|
- name: upload
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GAYHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||||
asset_path: x-ui-linux-arm64.tar.gz
|
asset_path: x-ui-linux-arm64.tar.gz
|
||||||
@@ -111,7 +112,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: '1.20'
|
||||||
- name: build linux s390x version
|
- name: build linux s390x version
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@@ -125,7 +126,7 @@ jobs:
|
|||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/hossinasaadi/Xray-core/releases/latest/download/Xray-linux-s390x.zip
|
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-s390x.zip
|
||||||
unzip Xray-linux-s390x.zip
|
unzip Xray-linux-s390x.zip
|
||||||
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
@@ -138,7 +139,7 @@ jobs:
|
|||||||
- name: upload
|
- name: upload
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GAYHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||||
asset_path: x-ui-linux-s390x.tar.gz
|
asset_path: x-ui-linux-s390x.tar.gz
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -1,7 +1,7 @@
|
|||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
tmp
|
tmp
|
||||||
bin/xray-darwin-arm64
|
bin/
|
||||||
bin/config.json
|
|
||||||
dist/
|
dist/
|
||||||
x-ui-*.tar.gz
|
x-ui-*.tar.gz
|
||||||
/x-ui
|
/x-ui
|
||||||
|
|||||||
21
Dockerfile
@@ -1,17 +1,16 @@
|
|||||||
FROM golang:latest AS builder
|
FROM golang:1.20-alpine AS builder
|
||||||
WORKDIR /root
|
WORKDIR /app
|
||||||
|
ENV CGO_ENABLED 1
|
||||||
|
RUN apk add gcc && apk --no-cache --update add build-base
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN go build main.go
|
RUN go build main.go
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||||
|
ENV TZ=Asia/Tehran
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
FROM debian:11-slim
|
RUN apk add ca-certificates tzdata && mkdir bin
|
||||||
LABEL org.opencontainers.image.authors="hossin.asaadi77@gmail.com"
|
COPY --from=builder /app/main /app/x-ui
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends -y ca-certificates \
|
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
ENV TZ=Asia/Shanghai
|
|
||||||
WORKDIR /root
|
|
||||||
COPY --from=builder /root/main /root/x-ui
|
|
||||||
COPY ./bin/. /root/bin/.
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
CMD [ "./x-ui" ]
|
CMD [ "./x-ui" ]
|
||||||
149
README.md
@@ -1,23 +1,29 @@
|
|||||||
# x-ui
|
# x-ui
|
||||||
|

|
||||||
|

|
||||||
|
[](https://goreportcard.com/report/github.com/alireza0/x-ui)
|
||||||
|
[](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
> **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**
|
||||||
|
|
||||||
|
|
||||||
xray panel supporting multi-protocol, **Multi-lang (English,Chinese)**, **IP Restrication Per Inbound**
|
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||||
|
|
||||||
| Features | Enable? |
|
| Features | Enable? |
|
||||||
| ------------- |:-------------:|
|
| ------------- |:-------------:|
|
||||||
| Multi-lang | :heavy_check_mark: |
|
| Multi-lang | :heavy_check_mark: |
|
||||||
| [IP Restriction](https://github.com/HexaSoftwareTech/x-ui/#enable-ip-restrictions-per-inbound) | :heavy_check_mark: |
|
| Search in deep | :heavy_check_mark: |
|
||||||
| [Inbound Multi User](https://github.com/HexaSoftwareTech/x-ui/#enable-multi-user-traffic--exprire-day) | :heavy_check_mark: |
|
| Inbound Multi User | :heavy_check_mark: |
|
||||||
| [Multi User Traffic & expire day](https://github.com/HexaSoftwareTech/x-ui/#enable-multi-user-traffic--exprire-day) | :heavy_check_mark: |
|
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
||||||
| [REST API](https://github.com/HexaSoftwareTech/x-ui/pull/51) | :heavy_check_mark: |
|
| REST API | :heavy_check_mark: |
|
||||||
| [Telegram BOT](https://github.com/HexaSoftwareTech/x-ui/pull/110) | :heavy_check_mark: |
|
| Telegram BOT | :heavy_check_mark: |
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- System Status Monitoring
|
- System Status Monitoring
|
||||||
|
- Search within all inbounds and clients
|
||||||
- Support multi-user multi-protocol, web page visualization operation
|
- Support multi-user multi-protocol, web page visualization operation
|
||||||
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
||||||
- Support for configuring more transport configurations
|
- Support for configuring more transport configurations
|
||||||
@@ -27,70 +33,19 @@ xray panel supporting multi-protocol, **Multi-lang (English,Chinese)**, **IP Res
|
|||||||
- Support one-click SSL certificate application and automatic renewal
|
- Support one-click SSL certificate application and automatic renewal
|
||||||
- For more advanced configuration items, please refer to the panel
|
- For more advanced configuration items, please refer to the panel
|
||||||
|
|
||||||
# Enable IP Restrictions Per Inbound
|
# Screenshoot from Inbouds page
|
||||||
`!!! NO NEED TO DO THIS IF YOU HAVE FRESH INSTALL`
|
|
||||||
|
|
||||||
1 - open panel settings and tab xray related settings find `"api": ` and put bellow code just before it :
|

|
||||||
```json
|
|
||||||
"log": {
|
|
||||||
"loglevel": "warning",
|
|
||||||
"access": "./access.log"
|
|
||||||
},
|
|
||||||
```
|
|
||||||
- change access log path as you want
|
|
||||||
|
|
||||||
2 - add **IP limit and Unique Email** for inbound(vmess & vless)
|
|
||||||
|
|
||||||
# Enable Multi User Traffic & Exprire Day
|
|
||||||

|
|
||||||
|
|
||||||
`!!! NO NEED TO DO THIS IF YOU HAVE FRESH INSTALL`
|
|
||||||
|
|
||||||
**for enable traffic for users you should do :**
|
|
||||||
|
|
||||||
find this in config :
|
|
||||||
``` json
|
|
||||||
"policy": {
|
|
||||||
"system": {
|
|
||||||
```
|
|
||||||
**and add this just after ` "policy": {` :**
|
|
||||||
```json
|
|
||||||
"levels": {
|
|
||||||
"0": {
|
|
||||||
"statsUserUplink": true,
|
|
||||||
"statsUserDownlink": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
**the final output is like :**
|
|
||||||
```json
|
|
||||||
"policy": {
|
|
||||||
"levels": {
|
|
||||||
"0": {
|
|
||||||
"statsUserUplink": true,
|
|
||||||
"statsUserDownlink": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"system": {
|
|
||||||
"statsInboundDownlink": true,
|
|
||||||
"statsInboundUplink": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"routing": {
|
|
||||||
```
|
|
||||||
restart panel
|
|
||||||
# Install & Upgrade
|
# Install & Upgrade
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/HexaSoftwareTech/x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manual install & upgrade
|
## Manual install & upgrade
|
||||||
|
|
||||||
1. First download the latest compressed package from https://github.com/HexaSoftwareTech/x-ui/releases , generally choose Architecture `amd64`
|
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases , generally choose Architecture `amd64`
|
||||||
2. Then upload the compressed package to the server's `/root/` directory and `root` rootlog in to the server with user
|
2. Then upload the compressed package to the server's `/root/` directory and `root` rootlog in to the server with user
|
||||||
|
|
||||||
> If your server cpu architecture is not `amd64` replace another architecture
|
> If your server cpu architecture is not `amd64` replace another architecture
|
||||||
@@ -110,7 +65,7 @@ systemctl restart x-ui
|
|||||||
|
|
||||||
## Install using docker
|
## Install using docker
|
||||||
|
|
||||||
> This docker tutorial and docker image are provided by [HexaSoftwareTech](https://github.com/HexaSoftwareTech)
|
> This docker tutorial and docker image are provided by [alireza0](https://github.com/alireza0)
|
||||||
|
|
||||||
1. install docker
|
1. install docker
|
||||||
|
|
||||||
@@ -126,7 +81,7 @@ docker run -itd --network=host \
|
|||||||
-v $PWD/db/:/etc/x-ui/ \
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
-v $PWD/cert/:/root/cert/ \
|
-v $PWD/cert/:/root/cert/ \
|
||||||
--name x-ui --restart=unless-stopped \
|
--name x-ui --restart=unless-stopped \
|
||||||
HexaSoftwareTech/x-ui:latest
|
alireza0/x-ui:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
> Build your own image
|
> Build your own image
|
||||||
@@ -137,27 +92,19 @@ docker build -t x-ui .
|
|||||||
|
|
||||||
## SSL certificate application
|
## SSL certificate application
|
||||||
|
|
||||||
|
### Cloudflare
|
||||||
|
|
||||||
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
|
||||||
|
|
||||||
The script has a built-in SSL certificate application function. To use this script to apply for a certificate, the following conditions must be met:
|
### Certbot
|
||||||
|
|
||||||
- Know the Cloudflare registered email
|
```bash
|
||||||
- Know the Cloudflare Global API Key
|
snap install core; snap refresh core
|
||||||
- The domain name has been resolved to the current server through cloudflare
|
snap install --classic certbot
|
||||||
|
ln -s /snap/bin/certbot /usr/bin/certbot
|
||||||
|
|
||||||
How to get the Cloudflare Global API Key:
|
certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
|
||||||

|
```
|
||||||

|
|
||||||
|
|
||||||
When using, just enter `email`, `domain`, `API KEY` and the schematic diagram is as follows:
|
|
||||||

|
|
||||||
|
|
||||||
Precautions:
|
|
||||||
|
|
||||||
- The script uses DNS API for certificate request
|
|
||||||
- By default, Let'sEncrypt is used as the CA party
|
|
||||||
- The certificate installation directory is the /root/cert directory
|
|
||||||
- The certificates applied for by this script are all generic domain name certificates
|
|
||||||
|
|
||||||
## Tg robot use (under development, temporarily unavailable)
|
## Tg robot use (under development, temporarily unavailable)
|
||||||
|
|
||||||
@@ -205,6 +152,46 @@ First install the latest version of x-ui on the server where v2-ui is installed,
|
|||||||
x-ui v2-ui
|
x-ui v2-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# T-Shoots:
|
||||||
|
|
||||||
|
**If you ygrade from an old version or other forks, for enable traffic for users you should do :**
|
||||||
|
|
||||||
|
find this in config :
|
||||||
|
``` json
|
||||||
|
"policy": {
|
||||||
|
"system": {
|
||||||
|
```
|
||||||
|
**and add this just after ` "policy": {` :**
|
||||||
|
```json
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserUplink": true,
|
||||||
|
"statsUserDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**the final output is like :**
|
||||||
|
```json
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserUplink": true,
|
||||||
|
"statsUserDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
```
|
||||||
|
restart panel
|
||||||
|
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
[](https://starchart.cc/HexaSoftwareTech/x-ui)
|
[](https://starchart.cc/alireza0/x-ui)
|
||||||
|
|||||||
BIN
bin/geoip.dat
16117
bin/geosite.dat
@@ -1 +1 @@
|
|||||||
0.5.3
|
0.2.3
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/xray"
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
@@ -41,9 +42,7 @@ func initInbound() error {
|
|||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
func initInboundClientIps() error {
|
|
||||||
return db.AutoMigrate(&model.InboundClientIps{})
|
|
||||||
}
|
|
||||||
func initClientTraffic() error {
|
func initClientTraffic() error {
|
||||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
@@ -83,10 +82,7 @@ func InitDB(dbPath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = initInboundClientIps()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initClientTraffic()
|
err = initClientTraffic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Inbound struct {
|
type Inbound struct {
|
||||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
UserId int `json:"-"`
|
UserId int `json:"-"`
|
||||||
Up int64 `json:"up" form:"up"`
|
Up int64 `json:"up" form:"up"`
|
||||||
Down int64 `json:"down" form:"down"`
|
Down int64 `json:"down" form:"down"`
|
||||||
Total int64 `json:"total" form:"total"`
|
Total int64 `json:"total" form:"total"`
|
||||||
Remark string `json:"remark" form:"remark"`
|
Remark string `json:"remark" form:"remark"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||||
|
|
||||||
// config part
|
// config part
|
||||||
Listen string `json:"listen" form:"listen"`
|
Listen string `json:"listen" form:"listen"`
|
||||||
@@ -43,11 +43,6 @@ type Inbound struct {
|
|||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
type InboundClientIps struct {
|
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
|
||||||
Ips string `json:"ips" form:"ips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||||
listen := i.Listen
|
listen := i.Listen
|
||||||
@@ -70,12 +65,12 @@ type Setting struct {
|
|||||||
Key string `json:"key" form:"key"`
|
Key string `json:"key" form:"key"`
|
||||||
Value string `json:"value" form:"value"`
|
Value string `json:"value" form:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
AlterIds uint16 `json:"alterId"`
|
AlterIds uint16 `json:"alterId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
Security string `json:"security"`
|
||||||
Security string `json:"security"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
}
|
}
|
||||||
BIN
db/x-ui.db
Normal file
@@ -1,7 +1,7 @@
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
services:
|
services:
|
||||||
xui:
|
xui:
|
||||||
image: hossinasaadi/x-ui
|
image: alireza7/x-ui
|
||||||
container_name: x-ui
|
container_name: x-ui
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD/db/:/etc/x-ui/
|
- $PWD/db/:/etc/x-ui/
|
||||||
|
|||||||
51
go.mod
@@ -1,27 +1,52 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.16
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v1.0.0
|
||||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Workiva/go-datastructures v1.0.53
|
||||||
github.com/gin-contrib/sessions v0.0.3
|
github.com/gin-contrib/sessions v0.0.3
|
||||||
github.com/gin-gonic/gin v1.7.1
|
github.com/gin-gonic/gin v1.7.1
|
||||||
github.com/go-cmd/cmd v1.4.1 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil v3.21.3+incompatible
|
github.com/shirou/gopsutil v3.21.3+incompatible
|
||||||
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
github.com/xtls/xray-core v1.7.5
|
||||||
github.com/xtls/xray-core v1.4.2
|
go.uber.org/atomic v1.10.0
|
||||||
go.uber.org/atomic v1.7.0
|
golang.org/x/text v0.6.0
|
||||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
|
google.golang.org/grpc v1.53.0
|
||||||
golang.org/x/text v0.3.6
|
|
||||||
google.golang.org/grpc v1.38.0
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gorm.io/driver/sqlite v1.1.4
|
gorm.io/driver/sqlite v1.1.4
|
||||||
gorm.io/gorm v1.21.9
|
gorm.io/gorm v1.21.9
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||||
|
github.com/go-playground/locales v0.13.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
|
github.com/gorilla/sessions v1.1.3 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.2.2 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||||
|
golang.org/x/crypto v0.5.0 // indirect
|
||||||
|
golang.org/x/net v0.5.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
61
go.sum
@@ -7,8 +7,9 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
|
|||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
|
||||||
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
|
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
|
||||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
||||||
@@ -43,7 +44,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||||
@@ -57,8 +57,6 @@ github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8=
|
|||||||
github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
|
||||||
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
@@ -74,7 +72,6 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a
|
|||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
@@ -168,7 +165,6 @@ github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGw
|
|||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
@@ -178,16 +174,13 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
|
||||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
@@ -198,6 +191,8 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rK
|
|||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
||||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
|
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||||
|
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -256,7 +251,6 @@ github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITn
|
|||||||
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
||||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
@@ -266,12 +260,17 @@ github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 h1:QHESTXtfgc1ABV+ArlbPVqU
|
|||||||
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
||||||
github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c=
|
github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c=
|
||||||
github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc=
|
github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc=
|
||||||
|
github.com/xtls/xray-core v1.7.5 h1:Ukr3hXnOG2ciViQL7kfYRl9S3GVej2dkV7DzabmoLL4=
|
||||||
|
github.com/xtls/xray-core v1.7.5/go.mod h1:Mx1QzIDvSk4eZ8hKa3AYsSPfyZJNQXWVXTJxJRJ98wI=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc h1:pVkptfeOTFfx+zXZo7HEHN3d5LmhatBFvHdm/f2QnpY=
|
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc h1:pVkptfeOTFfx+zXZo7HEHN3d5LmhatBFvHdm/f2QnpY=
|
||||||
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
@@ -280,14 +279,18 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||||
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -303,8 +306,11 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210330230544-e57232859fb2 h1:nGCZOty+lVDsc4H2qPFksI5Se296+V+GhMiL/TzmYNk=
|
|
||||||
golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||||
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@@ -317,8 +323,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -340,15 +347,23 @@ golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||||
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -361,6 +376,7 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -381,6 +397,8 @@ google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRn
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 h1:vArvWooPH749rNHpBGgVl+U9B9dATjiEhJzcWGlovNs=
|
||||||
|
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
@@ -391,6 +409,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
|||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
|
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||||
|
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -403,6 +423,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -410,7 +432,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
64
install.sh
@@ -26,7 +26,7 @@ elif cat /proc/version | grep -Eqi "ubuntu"; then
|
|||||||
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then
|
||||||
release="centos"
|
release="centos"
|
||||||
else
|
else
|
||||||
echo -e "${red} check system OS failed,please contact with author! ${plain}\n" && exit 1
|
echo -e "${red} Check system OS failed, please contact the author! ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
arch=$(arch)
|
arch=$(arch)
|
||||||
@@ -39,13 +39,13 @@ elif [[ $arch == "s390x" ]]; then
|
|||||||
arch="s390x"
|
arch="s390x"
|
||||||
else
|
else
|
||||||
arch="amd64"
|
arch="amd64"
|
||||||
echo -e "${red} Fail to check system arch,will use default arch here: ${arch}${plain}"
|
echo -e "${red} Fail to check system arch, will use default arch: ${arch}${plain}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "arch: ${arch}"
|
echo "arch: ${arch}"
|
||||||
|
|
||||||
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
||||||
echo "x-ui dosen't support 32bit(x86) system,please use 64 bit operating system(x86_64) instead,if there is something wrong,plz let me know"
|
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead,if there is something wrong, plz let me know"
|
||||||
exit -1
|
exit -1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -61,15 +61,15 @@ fi
|
|||||||
|
|
||||||
if [[ x"${release}" == x"centos" ]]; then
|
if [[ x"${release}" == x"centos" ]]; then
|
||||||
if [[ ${os_version} -le 6 ]]; then
|
if [[ ${os_version} -le 6 ]]; then
|
||||||
echo -e "${red} please use CentOS 7 or higher version ${plain}\n" && exit 1
|
echo -e "${red} Please use CentOS 7 or higher version ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
elif [[ x"${release}" == x"ubuntu" ]]; then
|
elif [[ x"${release}" == x"ubuntu" ]]; then
|
||||||
if [[ ${os_version} -lt 16 ]]; then
|
if [[ ${os_version} -lt 16 ]]; then
|
||||||
echo -e "${red} please use Ubuntu 16 or higher version ${plain}\n" && exit 1
|
echo -e "${red} Please use Ubuntu 16 or higher version ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
elif [[ x"${release}" == x"debian" ]]; then
|
elif [[ x"${release}" == x"debian" ]]; then
|
||||||
if [[ ${os_version} -lt 8 ]]; then
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
echo -e "${red} please use Debian 8 or higher version ${plain}\n" && exit 1
|
echo -e "${red} Please use Debian 8 or higher version ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -83,22 +83,22 @@ install_base() {
|
|||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
echo -e "${yellow} Install/update finished need to modify panel settings out of security ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
read -p "are you continue,if you type n will skip this at this time[y/n]": config_confirm
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||||
read -p "please set up your username:" config_account
|
read -p "Please set up your username:" config_account
|
||||||
echo -e "${yellow}your username will be:${config_account}${plain}"
|
echo -e "${yellow}Your username will be:${config_account}${plain}"
|
||||||
read -p "please set up your password:" config_password
|
read -p "Please set up your password:" config_password
|
||||||
echo -e "${yellow}your password will be:${config_password}${plain}"
|
echo -e "${yellow}Your password will be:${config_password}${plain}"
|
||||||
read -p "please set up the panel port:" config_port
|
read -p "Please set up the panel port:" config_port
|
||||||
echo -e "${yellow}your panel port is:${config_port}${plain}"
|
echo -e "${yellow}Your panel port is:${config_port}${plain}"
|
||||||
echo -e "${yellow}initializing,wait some time here...${plain}"
|
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||||
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
||||||
echo -e "${yellow}account name and password set down!${plain}"
|
echo -e "${yellow}Account name and password set successfully!${plain}"
|
||||||
/usr/local/x-ui/x-ui setting -port ${config_port}
|
/usr/local/x-ui/x-ui setting -port ${config_port}
|
||||||
echo -e "${yellow}panel port set down!${plain}"
|
echo -e "${yellow}Panel port set successfully!${plain}"
|
||||||
else
|
else
|
||||||
echo -e "${red}Canceled, all setting items are default settings${plain}"
|
echo -e "${red}Canceled, will use the default settings.${plain}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,21 +107,21 @@ install_x-ui() {
|
|||||||
cd /usr/local/
|
cd /usr/local/
|
||||||
|
|
||||||
if [ $# == 0 ]; then
|
if [ $# == 0 ]; then
|
||||||
last_version=$(curl -Ls "https://api.github.com/repos/hossinasaadi/x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
last_version=$(curl -Ls "https://api.github.com/repos/alireza0/x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
if [[ ! -n "$last_version" ]]; then
|
if [[ ! -n "$last_version" ]]; then
|
||||||
echo -e "${red}refresh x-ui version failed,it may due to Github API restriction,please try it later${plain}"
|
echo -e "${red}Failed to fetch x-ui version, it maybe due to Github API restrictions, please try it later${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "get x-ui latest version succeed: ${last_version}, begin to install..."
|
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
|
||||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/hossinasaadi/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}dowanload x-ui failed,please be sure that your server can access Github ${plain}"
|
echo -e "${red}Dowanloading x-ui failed, please be sure that your server can access Github ${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
last_version=$1
|
last_version=$1
|
||||||
url="https://github.com/hossinasaadi/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||||
echo -e "begin to install x-ui v$1"
|
echo -e "Begining to install x-ui v$1"
|
||||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}dowanload x-ui v$1 failed,please check the verison exists${plain}"
|
echo -e "${red}dowanload x-ui v$1 failed,please check the verison exists${plain}"
|
||||||
@@ -138,20 +138,20 @@ install_x-ui() {
|
|||||||
cd x-ui
|
cd x-ui
|
||||||
chmod +x x-ui bin/xray-linux-${arch}
|
chmod +x x-ui bin/xray-linux-${arch}
|
||||||
cp -f x-ui.service /etc/systemd/system/
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/hossinasaadi/x-ui/main/x-ui.sh
|
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/alireza0/x-ui/main/x-ui.sh
|
||||||
chmod +x /usr/local/x-ui/x-ui.sh
|
chmod +x /usr/local/x-ui/x-ui.sh
|
||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
config_after_install
|
config_after_install
|
||||||
#echo -e "如果是全新安装,默认网页端口为 ${green}54321${plain},用户名和密码默认都是 ${green}admin${plain}"
|
#echo -e "If it is a new installation, the default web port is ${green}54321${plain}, The username and password are ${green}admin${plain} by default"
|
||||||
#echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保 54321 端口已放行${plain}"
|
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 54321 has been released${plain}"
|
||||||
# echo -e "若想将 54321 修改为其它端口,输入 x-ui 命令进行修改,同样也要确保你修改的端口也是放行的"
|
# echo -e "If you want to modify the 54321 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
|
||||||
#echo -e ""
|
#echo -e ""
|
||||||
#echo -e "如果是更新面板,则按你之前的方式访问面板"
|
#echo -e "If it is updated panel, access the panel in your previous way"
|
||||||
#echo -e ""
|
#echo -e ""
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable x-ui
|
systemctl enable x-ui
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}x-ui v${last_version}${plain} install finished,it is working now..."
|
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "x-ui control menu usages: "
|
echo -e "x-ui control menu usages: "
|
||||||
echo -e "----------------------------------------------"
|
echo -e "----------------------------------------------"
|
||||||
@@ -170,6 +170,6 @@ install_x-ui() {
|
|||||||
echo -e "----------------------------------------------"
|
echo -e "----------------------------------------------"
|
||||||
}
|
}
|
||||||
|
|
||||||
echo -e "${green}excuting...${plain}"
|
echo -e "${green}Excuting...${plain}"
|
||||||
install_base
|
install_base
|
||||||
install_x-ui $1
|
install_x-ui $1
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB |
BIN
media/inbounds.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
2
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
@@ -145,3 +145,8 @@
|
|||||||
.ant-progress-inner {
|
.ant-progress-inner {
|
||||||
background-color: #EBEEF5;
|
background-color: #EBEEF5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deactive-client .ant-collapse-header{
|
||||||
|
color:rgb(255, 255, 255) !important;
|
||||||
|
background-color: rgb(255, 127, 127);
|
||||||
|
}
|
||||||
@@ -4,6 +4,11 @@ supportLangs = [
|
|||||||
value : "en-US",
|
value : "en-US",
|
||||||
icon : "🇺🇸"
|
icon : "🇺🇸"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name : "Farsi",
|
||||||
|
value : "fa_IR",
|
||||||
|
icon : "🇮🇷"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name : "汉语",
|
name : "汉语",
|
||||||
value : "zh-Hans",
|
value : "zh-Hans",
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink() {
|
genLink(clientIndex) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(this.address, this.remark);
|
return inbound.genLink(this.address, this.remark, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ class AllSetting {
|
|||||||
this.tgRunTime = "";
|
this.tgRunTime = "";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
|
|
||||||
this.timeLocation = "Asia/Shanghai";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -825,6 +825,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
if(this.settings.vlesses[index]._expiryTime != null)
|
if(this.settings.vlesses[index]._expiryTime != null)
|
||||||
return this.settings.vlesses[index]._expiryTime < new Date().getTime();
|
return this.settings.vlesses[index]._expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
|
case Protocols.TROJAN:
|
||||||
|
if(this.settings.trojans[index]._expiryTime != null)
|
||||||
|
return this.settings.trojans[index]._expiryTime < new Date().getTime();
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1053,17 +1057,29 @@ class Inbound extends XrayCommonClass {
|
|||||||
+ '#' + encodeURIComponent(remark);
|
+ '#' + encodeURIComponent(remark);
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address='', remark='') {
|
genTrojanLink(address='', remark='', clientIndex=0) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
return `trojan://${settings.clients[0].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
return `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', remark='', clientIndex=0) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS: return this.genVmessLink(address, remark, clientIndex);
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS: return this.genVLESSLink(address, remark, clientIndex);
|
if (this.settings.vmesses[clientIndex].email != ""){
|
||||||
|
remark += '-' + this.settings.vmesses[clientIndex].email
|
||||||
|
}
|
||||||
|
return this.genVmessLink(address, remark, clientIndex);
|
||||||
|
case Protocols.VLESS:
|
||||||
|
if (this.settings.vlesses[clientIndex].email != ""){
|
||||||
|
remark += '-' + this.settings.vlesses[clientIndex].email
|
||||||
|
}
|
||||||
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
||||||
case Protocols.TROJAN: return this.genTrojanLink(address, remark);
|
case Protocols.TROJAN:
|
||||||
|
if (this.settings.trojans[clientIndex].email != ""){
|
||||||
|
remark += '-' + this.settings.trojans[clientIndex].email
|
||||||
|
}
|
||||||
|
return this.genTrojanLink(address, remark, clientIndex);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1181,12 +1197,11 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), alterId=0, email='', limitIp=0, totalGB=0, expiryTime='') {
|
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
this.alterId = alterId;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
}
|
}
|
||||||
@@ -1196,7 +1211,6 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
json.id,
|
json.id,
|
||||||
json.alterId,
|
json.alterId,
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
|
||||||
@@ -1265,12 +1279,11 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
|
|
||||||
constructor(id=RandomUtil.randomUUID(), flow=FLOW_CONTROL.DIRECT, email='', limitIp=0, totalGB=0, expiryTime='') {
|
constructor(id=RandomUtil.randomUUID(), flow=FLOW_CONTROL.DIRECT, email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
|
||||||
@@ -1281,7 +1294,6 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||||||
json.id,
|
json.id,
|
||||||
json.flow,
|
json.flow,
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
|
||||||
@@ -1351,10 +1363,10 @@ Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
|||||||
|
|
||||||
Inbound.TrojanSettings = class extends Inbound.Settings {
|
Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
clients=[new Inbound.TrojanSettings.Client()],
|
trojans=[new Inbound.TrojanSettings.Trojan()],
|
||||||
fallbacks=[],) {
|
fallbacks=[],) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.clients = clients;
|
this.trojans = trojans;
|
||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1366,45 +1378,73 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
this.fallbacks.splice(index, 1);
|
this.fallbacks.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Inbound.TrojanSettings(
|
||||||
|
Protocols.TROJAN,
|
||||||
|
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
||||||
|
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
||||||
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.TrojanSettings.toJsonArray(this.clients),
|
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
||||||
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
|
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
const clients = [];
|
|
||||||
for (const c of json.clients) {
|
|
||||||
clients.push(Inbound.TrojanSettings.Client.fromJson(c));
|
|
||||||
}
|
|
||||||
return new Inbound.TrojanSettings(
|
|
||||||
Protocols.TROJAN,
|
|
||||||
clients,
|
|
||||||
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Client = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow=FLOW_CONTROL.DIRECT) {
|
constructor(password=RandomUtil.randomSeq(10), flow=FLOW_CONTROL.DIRECT, email=RandomUtil.randomText(), totalGB=0, expiryTime='') {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
|
this.email = email;
|
||||||
|
this.totalGB = totalGB;
|
||||||
|
this.expiryTime = expiryTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
password: this.password,
|
password: this.password,
|
||||||
flow: this.flow,
|
flow: this.flow,
|
||||||
|
email: this.email,
|
||||||
|
totalGB: this.totalGB,
|
||||||
|
expiryTime: this.expiryTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new Inbound.TrojanSettings.Client(
|
return new Inbound.TrojanSettings.Trojan(
|
||||||
json.password,
|
json.password,
|
||||||
json.flow,
|
json.flow,
|
||||||
|
json.email,
|
||||||
|
json.totalGB,
|
||||||
|
json.expiryTime,
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _expiryTime() {
|
||||||
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return moment(this.expiryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _expiryTime(t) {
|
||||||
|
if (t == null || t === "") {
|
||||||
|
this.expiryTime = 0;
|
||||||
|
} else {
|
||||||
|
this.expiryTime = t.valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get _totalGB() {
|
||||||
|
return toFixed(this.totalGB / ONE_GB, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _totalGB(gb) {
|
||||||
|
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
||||||
|
|||||||
@@ -55,3 +55,15 @@ function toFixed(num, n) {
|
|||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.round(num * n) / n;
|
return Math.round(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -136,6 +136,16 @@ class RandomUtil {
|
|||||||
return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randomText() {
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboundController struct {
|
type InboundController struct {
|
||||||
@@ -30,12 +31,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
|
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
|
||||||
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) startTask() {
|
func (a *InboundController) startTask() {
|
||||||
@@ -55,7 +52,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbounds, nil)
|
jsonObj(c, inbounds, nil)
|
||||||
@@ -63,12 +60,12 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
func (a *InboundController) getInbound(c *gin.Context) {
|
func (a *InboundController) getInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "get"), err)
|
jsonMsg(c, I18n(c, "get"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err := a.inboundService.GetInbound(id)
|
inbound, err := a.inboundService.GetInbound(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
@@ -78,7 +75,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "pages.inbounds.addTo"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.addTo"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
@@ -86,7 +83,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound.Enable = true
|
inbound.Enable = true
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
inbound, err = a.inboundService.AddInbound(inbound)
|
inbound, err = a.inboundService.AddInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c , "pages.inbounds.addTo"), inbound, err)
|
jsonMsgObj(c, I18n(c, "pages.inbounds.addTo"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -95,11 +92,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) delInbound(c *gin.Context) {
|
func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "delete"), err)
|
jsonMsg(c, I18n(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelInbound(id)
|
err = a.inboundService.DelInbound(id)
|
||||||
jsonMsgObj(c, I18n(c , "delete"), id, err)
|
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -108,7 +105,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
@@ -116,35 +113,16 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = c.ShouldBind(inbound)
|
err = c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c , "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
inbound, err = a.inboundService.UpdateInbound(inbound)
|
||||||
jsonMsgObj(c, I18n(c , "pages.inbounds.revise"), inbound, err)
|
jsonMsgObj(c, I18n(c, "pages.inbounds.revise"), inbound, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (a *InboundController) getClientIps(c *gin.Context) {
|
|
||||||
email := c.Param("email")
|
|
||||||
|
|
||||||
ips , err := a.inboundService.GetInboundClientIps(email)
|
|
||||||
if err != nil {
|
|
||||||
jsonObj(c, "No IP Record", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, ips, nil)
|
|
||||||
}
|
|
||||||
func (a *InboundController) clearClientIps(c *gin.Context) {
|
|
||||||
email := c.Param("email")
|
|
||||||
|
|
||||||
err := a.inboundService.ClearClientIps(email)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "修改", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
|
||||||
}
|
|
||||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,7 @@
|
|||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:closable="true" width="300px" :ok-text="qrModal.okText"
|
:closable="true" width="300px" :ok-text="qrModal.okText"
|
||||||
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
|
cancel-text='{{ i18n "close" }}' :ok-button-props="{attrs:{id:'qr-modal-ok-btn'}}">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >click on QR Code to Copy</a-tag>
|
<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
||||||
<canvas v-if="qrModal.inbound.protocol != Protocols.VMESS && qrModal.inbound.protocol != Protocols.VLESS" id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
|
||||||
|
|
||||||
<template v-if="qrModal.inbound.protocol === Protocols.VMESS" v-for="(vmess, index) in qrModal.inbound.settings.vmesses">
|
|
||||||
<a-tag color="red" style="margin-bottom: 10px;display: block;text-align: center;" v-text="vmess.email"></a-tag>
|
|
||||||
<canvas @click="copyTextToClipboard(`qrCode-vmess-${vmess.id}`,index)" :id="`qrCode-vmess-${vmess.id}`" style="width: 100%; height: 100%;"></canvas>
|
|
||||||
<a-divider style="height: 2px; background-color: #7e7e7e" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="qrModal.inbound.protocol === Protocols.VLESS" v-for="(vless, index) in qrModal.inbound.settings.vlesses">
|
|
||||||
<a-tag color="red" style="margin-bottom: 10px;display: block;text-align: center;" v-text="vless.email"></a-tag>
|
|
||||||
<canvas @click="copyTextToClipboard(`qrCode-vless-${vless.id}`,index)" :id="`qrCode-vless-${vless.id}`" style="width: 100%; height: 100%;"></canvas>
|
|
||||||
<a-divider style="height: 2px; background-color: #7e7e7e" />
|
|
||||||
</template>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -70,50 +57,6 @@
|
|||||||
data: {
|
data: {
|
||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
setQrCode(elmentId,index) {
|
|
||||||
content = qrModal.inbound.genLink(qrModal.dbInbound.address,qrModal.dbInbound.remark,index)
|
|
||||||
|
|
||||||
new QRious({
|
|
||||||
element: document.querySelector('#'+elmentId),
|
|
||||||
size: 260,
|
|
||||||
value: content,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
copyTextToClipboard(elmentId,index) {
|
|
||||||
link = qrModal.inbound.genLink(qrModal.dbInbound.address,qrModal.dbInbound.remark,index)
|
|
||||||
this.qrModal.copyText = link
|
|
||||||
|
|
||||||
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
|
|
||||||
text: () => link,
|
|
||||||
});
|
|
||||||
this.qrModal.clipboard.on('success', () => {
|
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
|
||||||
this.qrModal.clipboard.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
switch (qrModal.inbound.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
vmesses = qrModal.inbound.settings.vmesses
|
|
||||||
for (const index in vmesses) {
|
|
||||||
this.setQrCode("qrCode-vmess-" + vmesses[index].id ,index)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Protocols.VLESS:
|
|
||||||
vlesses = qrModal.inbound.settings.vlesses
|
|
||||||
|
|
||||||
for (const index in vlesses) {
|
|
||||||
this.setQrCode("qrCode-vless-" + vlesses[index].id ,index)
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>客户端</span>-->
|
<!-- <span>Client</span>-->
|
||||||
<!--</a-menu-item>-->
|
<!--</a-menu-item>-->
|
||||||
<a-sub-menu>
|
<a-sub-menu>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<a-icon type="link"></a-icon>
|
<a-icon type="link"></a-icon>
|
||||||
<span>{{ i18n "menu.link"}}</span>
|
<span>{{ i18n "menu.link"}}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-menu-item key="https://github.com/hossinasaadi/x-ui/">
|
<a-menu-item key="https://github.com/alireza0/x-ui/">
|
||||||
<a-icon type="github"></a-icon>
|
<a-icon type="github"></a-icon>
|
||||||
<span>Github</span>
|
<span>Github</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
{{define "inboundInfoStream"}}
|
|
||||||
<p>{{ i18n "transmission" }}: <a-tag color="green">[[ inbound.network ]]</a-tag></p>
|
|
||||||
|
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
|
||||||
<p v-if="inbound.host">host: <a-tag color="green">[[ inbound.host ]]</a-tag></p>
|
|
||||||
<p v-else>{{ i18n "host" }}: <a-tag color="orange">{{ i18n "none" }}</a-tag></p>
|
|
||||||
|
|
||||||
<p v-if="inbound.path">path: <a-tag color="green">[[ inbound.path ]]</a-tag></p>
|
|
||||||
<p v-else>{{ i18n "path" }}: <a-tag color="orange">{{ i18n "none" }}</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.isQuic">
|
|
||||||
<p>quic {{ i18n "encryption" }}: <a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></p>
|
|
||||||
<p>quic {{ i18n "password" }}: <a-tag color="green">[[ inbound.quicKey ]]</a-tag></p>
|
|
||||||
<p>quic {{ i18n "camouflage" }}: <a-tag color="green">[[ inbound.quicType ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.isKcp">
|
|
||||||
<p>kcp {{ i18n "encryption" }}: <a-tag color="green">[[ inbound.kcpType ]]</a-tag></p>
|
|
||||||
<p>kcp {{ i18n "password" }}: <a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.isGrpc">
|
|
||||||
<p>grpc serviceName: <a-tag color="green">[[ inbound.serviceName ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.tls || inbound.xtls">
|
|
||||||
<p v-if="inbound.tls">tls: <a-tag color="green">{{ i18n "turnOn" }}</a-tag></p>
|
|
||||||
<p v-if="inbound.xtls">xtls: <a-tag color="green">{{ i18n "turnOn" }}</a-tag></p>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<p>tls: <a-tag color="red">{{ i18n "closure" }}</a-tag></p>
|
|
||||||
</template>
|
|
||||||
<p v-if="inbound.tls">
|
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
|
||||||
</p>
|
|
||||||
<p v-if="inbound.xtls">
|
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
{{define "component/inboundInfoComponent"}}
|
|
||||||
<div>
|
|
||||||
<p>{{ i18n "protocol"}}: <a-tag color="green">[[ dbInbound.protocol ]]</a-tag></p>
|
|
||||||
<p>{{ i18n "pages.inbounds.address"}}: <a-tag color="blue">[[ dbInbound.address ]]</a-tag></p>
|
|
||||||
<p>{{ i18n "pages.inbounds.port"}}: <a-tag color="green">[[ dbInbound.port ]]</a-tag></p>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isVMess" v-for="(vmess, index) in inbound.settings.vmesses">
|
|
||||||
<p>uuid: <a-tag color="green">[[ vmess.id ]]</a-tag></p>
|
|
||||||
<p>alterId: <a-tag color="green">[[ vmess.alterId ]]</a-tag></p>
|
|
||||||
<a-divider style="height: 2px; background-color: #7e7e7e" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isVLess" v-for="(vless, index) in inbound.settings.vlesses">
|
|
||||||
<p>uuid: <a-tag color="green">[[ vless.id ]]</a-tag></p>
|
|
||||||
<p v-if="inbound.isXTls">flow: <a-tag color="green">[[ vless.flow ]]</a-tag></p>
|
|
||||||
<a-divider style="height: 2px; background-color: #7e7e7e" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isTrojan">
|
|
||||||
<p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isSS">
|
|
||||||
<p>{{ i18n "encryption"}}: <a-tag color="green">[[ inbound.method ]]</a-tag></p>
|
|
||||||
<p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isSocks">
|
|
||||||
<p>{{ i18n "username"}}: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
|
|
||||||
<p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isHTTP">
|
|
||||||
<p>{{ i18n "username"}}: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
|
|
||||||
<p>{{ i18n "password"}}: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
|
||||||
{{template "inboundInfoStream"}}
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "component/inboundInfo"}}
|
|
||||||
<script>
|
|
||||||
Vue.component('inbound-info', {
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
props: ["dbInbound", "inbound"],
|
|
||||||
template: `{{template "component/inboundInfoComponent"}}`,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<!-- <a-form-item label="密码认证">-->
|
<!-- <a-form-item label="Password authentication">-->
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
|
|||||||
@@ -1,15 +1,109 @@
|
|||||||
{{define "form/trojan"}}
|
{{define "form/trojan"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<label>{{ i18n "clients"}} </label>
|
||||||
<a-input v-model.trim="inbound.settings.clients[0].password"></a-input>
|
<a-collapse activeKey="0" v-for="(trojan, index) in inbound.settings.trojans"
|
||||||
</a-form-item>
|
:key="`trojan-${index}`">
|
||||||
<a-form-item v-if="inbound.xtls" label="flow">
|
|
||||||
<a-select v-model="inbound.settings.clients[0].flow" style="width: 150px">
|
<a-collapse-panel :class="getHeaderStyle(trojan.email)" :header="getHeaderText(trojan.email)">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-tag v-if="isExpiry(index) || ((getUpStats(trojan.email) + getDownStats(trojan.email)) > trojan.totalGB && trojan.totalGB != 0)" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
<a-select-option v-for="key in FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-form layout="inline">
|
||||||
</a-select>
|
<a-form-item>
|
||||||
</a-form-item>
|
<span slot="label">
|
||||||
</a-form>
|
Email
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
The email must be completely unique
|
||||||
|
</template>
|
||||||
|
<!--Renew Svg Icon-->
|
||||||
|
<svg
|
||||||
|
@click="getNewEmail(trojan)"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="anticon anticon-question-circle" viewBox="0 0 16 16"> <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z"/> <path fill-rule="evenodd" d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"/> </svg>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input v-model.trim="trojan.email"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-form-item label="password">
|
||||||
|
<a-input v-model.trim="trojan.password"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="inbound.xtls" label="flow">
|
||||||
|
<a-select v-model="trojan.flow" style="width: 150px">
|
||||||
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in 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)
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input-number v-model="trojan._totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||||
|
v-model="trojan._expiryTime" style="width: 300px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-tooltip v-if="trojan._totalGB > 0">
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</template>
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete" @click="resetClientTraffic(trojan,$event)"></a-icon>
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tag color="blue">[[ sizeFormat(getUpStats(trojan.email)) ]] / [[ sizeFormat(getDownStats(trojan.email)) ]]</a-tag>
|
||||||
|
<a-tag v-if="trojan._totalGB > 0" color="red">used : [[ sizeFormat(getUpStats(trojan.email) + getDownStats(trojan.email)) ]]</a-tag>
|
||||||
|
<a-tag v-show="inbound.settings.trojans.length > 1">
|
||||||
|
<svg
|
||||||
|
v-show="inbound.settings.trojans.length > 1"
|
||||||
|
@click="removeClient(index, inbound.settings.trojans)"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
class="ml-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
fill="#EC4899"
|
||||||
|
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-9.414l2.828-2.829 1.415 1.415L13.414 12l2.829 2.828-1.415 1.415L12 13.414l-2.828 2.829-1.415-1.415L10.586 12 7.757 9.172l1.415-1.415L12 10.586z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a-tag>
|
||||||
|
</a-form>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-tag>
|
||||||
|
<svg
|
||||||
|
@click="addClient(inbound.protocol, inbound.settings.trojans)"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
class="ml-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
fill="green"
|
||||||
|
d="M11 11V7h2v4h4v2h-4v4h-2v-4H7v-2h4zm1 11C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a-tag>
|
||||||
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="fallbacks">
|
<a-form-item label="fallbacks">
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<label>{{ i18n "clients"}}</label>
|
||||||
<a-collapse activeKey="0" v-for="(vless, index) in inbound.settings.vlesses"
|
<a-collapse activeKey="0" v-for="(vless, index) in inbound.settings.vlesses"
|
||||||
:key="`vless-${index}`">
|
:key="`vless-${index}`">
|
||||||
|
|
||||||
<a-collapse-panel :header="getHeaderText(vless.email)">
|
<a-collapse-panel :class="getHeaderStyle(vless.email)" :header="getHeaderText(vless.email)">
|
||||||
<a-tag v-if="isExpiry(index) || ((getUpStats(vless.email) + getDownStats(vless.email)) > vless.totalGB && vless.totalGB != 0)" 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(index) || ((getUpStats(vless.email) + getDownStats(vless.email)) > vless.totalGB && vless.totalGB != 0)" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
@@ -22,43 +23,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="vless.email"></a-input>
|
<a-input v-model.trim="vless.email"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
IP Count Limit
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
disable inbound if more than entered count (0 for disable limit ip)
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<a-input type="number" v-model.number="vless.limitIp" min="0" ></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="vless.email && vless.limitIp > 0 && isEdit">
|
|
||||||
<span slot="label">
|
|
||||||
IP log
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
clear the log
|
|
||||||
</template>
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete" @click="clearDBClientIps(vless.email,$event)"></a-icon>
|
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-form layout="block">
|
|
||||||
|
|
||||||
<a-textarea readonly @click="getDBClientIps(vless.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }">
|
|
||||||
</a-textarea>
|
|
||||||
</a-form>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="id">
|
<a-form-item label="id">
|
||||||
<a-input v-model.trim="vless.id"></a-input>
|
<a-input v-model.trim="vless.id"></a-input>
|
||||||
@@ -97,7 +61,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-tooltip v-if="vless._totalGB > 0">
|
<a-tooltip v-if="vless._totalGB > 0">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
reset traffic
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="resetClientTraffic(vless,$event)"></a-icon>
|
<a-icon type="delete" @click="resetClientTraffic(vless,$event)"></a-icon>
|
||||||
@@ -105,23 +69,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="blue">[[ sizeFormat(getUpStats(vless.email)) ]] / [[ sizeFormat(getDownStats(vless.email)) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(getUpStats(vless.email)) ]] / [[ sizeFormat(getDownStats(vless.email)) ]]</a-tag>
|
||||||
<a-tag v-if="vless._totalGB > 0" color="red">used : [[ sizeFormat(getUpStats(vless.email) + getDownStats(vless.email)) ]]</a-tag>
|
<a-tag v-if="vless._totalGB > 0" color="red">used : [[ sizeFormat(getUpStats(vless.email) + getDownStats(vless.email)) ]]</a-tag>
|
||||||
<a-tag>
|
|
||||||
<svg
|
|
||||||
|
|
||||||
@click="addClient(inbound.protocol,vless, inbound.settings.vlesses)"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 22 22"
|
|
||||||
width="22"
|
|
||||||
height="22"
|
|
||||||
class="mt-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
<path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
<path
|
|
||||||
fill="green"
|
|
||||||
d="M11 11V7h2v4h4v2h-4v4h-2v-4H7v-2h4zm1 11C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a-tag>
|
|
||||||
<a-tag v-show="inbound.settings.vlesses.length > 1">
|
<a-tag v-show="inbound.settings.vlesses.length > 1">
|
||||||
<svg
|
<svg
|
||||||
@click="removeClient(index, inbound.settings.vlesses)"
|
@click="removeClient(index, inbound.settings.vlesses)"
|
||||||
@@ -139,9 +87,24 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
</a-form>
|
<a-tag>
|
||||||
|
<svg
|
||||||
|
@click="addClient(inbound.protocol, inbound.settings.vlesses)"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
class="ml-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
fill="green"
|
||||||
|
d="M11 11V7h2v4h4v2h-4v4h-2v-4H7v-2h4zm1 11C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a-tag>
|
||||||
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="fallbacks">
|
<a-form-item label="fallbacks">
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<label>{{ i18n "clients"}}</label>
|
||||||
<a-collapse activeKey="0" v-for="(vmess, index) in inbound.settings.vmesses"
|
<a-collapse activeKey="0" v-for="(vmess, index) in inbound.settings.vmesses"
|
||||||
:key="`vmess-${index}`">
|
:key="`vmess-${index}`">
|
||||||
<a-collapse-panel :header="getHeaderText(vmess.email)">
|
<a-collapse-panel :class="getHeaderStyle(vmess.email)" :header="getHeaderText(vmess.email)">
|
||||||
<a-tag v-if="isExpiry(index) || ((getUpStats(vmess.email) + getDownStats(vmess.email)) > vmess.totalGB && vmess.totalGB != 0)" 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(index) || ((getUpStats(vmess.email) + getDownStats(vmess.email)) > vmess.totalGB && vmess.totalGB != 0)" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
@@ -21,42 +22,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="vmess.email"></a-input>
|
<a-input v-model.trim="vmess.email"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
IP Count Limit
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
disable inbound if more than entered count (0 for disable limit ip)
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<a-input type="number" v-model.number="vmess.limitIp" min="0" ></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="vmess.email && vmess.limitIp > 0 && isEdit">
|
|
||||||
<span slot="label">
|
|
||||||
IP Log
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
clear the log
|
|
||||||
</template>
|
|
||||||
<span style="color: #FF4D4F">
|
|
||||||
<a-icon type="delete" @click="clearDBClientIps(vmess.email,$event)"></a-icon>
|
|
||||||
</span>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<a-textarea readonly @click="getDBClientIps(vmess.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }">
|
|
||||||
</a-textarea>
|
|
||||||
</a-form-item>
|
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="id">
|
<a-form-item label="id">
|
||||||
<a-input v-model.trim="vmess.id"></a-input>
|
<a-input v-model.trim="vmess.id"></a-input>
|
||||||
@@ -92,7 +57,7 @@
|
|||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-tooltip v-if="vmess._totalGB > 0">
|
<a-tooltip v-if="vmess._totalGB > 0">
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
reset traffic
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="resetClientTraffic(vmess,$event)"></a-icon>
|
<a-icon type="delete" @click="resetClientTraffic(vmess,$event)"></a-icon>
|
||||||
@@ -100,26 +65,7 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tag color="blue">[[ sizeFormat(getUpStats(vmess.email)) ]] / [[ sizeFormat(getDownStats(vmess.email)) ]]</a-tag>
|
<a-tag color="blue">[[ sizeFormat(getUpStats(vmess.email)) ]] / [[ sizeFormat(getDownStats(vmess.email)) ]]</a-tag>
|
||||||
<a-tag v-if="vmess._totalGB > 0" color="red">used : [[ sizeFormat(getUpStats(vmess.email) + getDownStats(vmess.email)) ]]</a-tag>
|
<a-tag v-if="vmess._totalGB > 0" color="red">used : [[ sizeFormat(getUpStats(vmess.email) + getDownStats(vmess.email)) ]]</a-tag>
|
||||||
<a-tag>
|
|
||||||
<!--Add Svg Icon-->
|
|
||||||
<svg
|
|
||||||
|
|
||||||
@click="addClient(inbound.protocol,vmess, inbound.settings.vmesses)"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 22 22"
|
|
||||||
width="22"
|
|
||||||
height="22"
|
|
||||||
class="mt-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
<path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
<path
|
|
||||||
fill="green"
|
|
||||||
d="M11 11V7h2v4h4v2h-4v4h-2v-4H7v-2h4zm1 11C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a-tag>
|
|
||||||
<a-tag v-show="inbound.settings.vmesses.length > 1">
|
<a-tag v-show="inbound.settings.vmesses.length > 1">
|
||||||
|
|
||||||
<!--Remove Svg Icon-->
|
<!--Remove Svg Icon-->
|
||||||
<svg
|
<svg
|
||||||
@click="removeClient(index, inbound.settings.vmesses)"
|
@click="removeClient(index, inbound.settings.vmesses)"
|
||||||
@@ -143,9 +89,23 @@
|
|||||||
|
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
|
|
||||||
|
<a-tag>
|
||||||
|
<svg
|
||||||
|
@click="addClient(inbound.protocol, inbound.settings.vmesses)"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
class="ml-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
fill="green"
|
||||||
|
d="M11 11V7h2v4h4v2h-4v4h-2v-4H7v-2h4zm1 11C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a-tag>
|
||||||
|
|
||||||
</a-form>
|
|
||||||
</a-form>
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<a-form-item label="acceptProxyProtocol">
|
<a-form-item label="acceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="http 伪装">
|
<a-form-item label="http {{ i18n "camouflage" }}">
|
||||||
<a-switch
|
<a-switch
|
||||||
:checked="inbound.stream.tcp.type === 'http'"
|
:checked="inbound.stream.tcp.type === 'http'"
|
||||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
|
|||||||
34
web/html/xui/inbound_client_table.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{{define "client_table"}}
|
||||||
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
|
<a-dropdown>
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record,index);"><a-icon type="qrcode"></a-icon>{{ i18n "qrCode" }}</a-menu-item>
|
||||||
|
<a-menu-item @click="showInfo(record,index);"><a-icon type="info-circle"></a-icon>{{ i18n "info" }}</a-menu-item>
|
||||||
|
<a-menu-item @click="resetClientTraffic(client,record,$event)" v-if="client.email != ''"><a-icon type="retweet"></a-icon>{{ i18n "pages.inbounds.resetTraffic" }}</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="client" slot-scope="text, client">
|
||||||
|
[[ client.email ]]
|
||||||
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "disabled" }}</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>
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
|
<template v-if="client._expiryTime > 0">
|
||||||
|
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
|
||||||
|
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
||||||
@@ -1,9 +1,103 @@
|
|||||||
{{define "inboundInfoModal"}}
|
{{define "inboundInfoModal"}}
|
||||||
{{template "component/inboundInfo"}}
|
<a-modal id="inbound-info-modal"
|
||||||
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' @ok="infoModal.ok"
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true" :mask-closable="true"
|
:closable="true"
|
||||||
ok-text='{{ i18n "pages.inbounds.copyLink"}}' cancel-text='{{ i18n "close" }}' :ok-button-props="infoModal.okBtnPros">
|
:mask-closable="true"
|
||||||
<inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
|
: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>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
<tr colspan="2">
|
||||||
|
<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>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr><th>[[ Object.keys(infoModal.clientSettings)[0] ]]</th><th>[[ Object.keys(infoModal.clientSettings)[1] ]]</th><th>[[ Object.keys(infoModal.clientSettings)[2] ]]</th></tr>
|
||||||
|
<tr>
|
||||||
|
<td><a-tag color="green">[[ Object.values(infoModal.clientSettings)[0] ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ Object.values(infoModal.clientSettings)[1] ]]</a-tag></td>
|
||||||
|
<td><a-tag color="green">[[ Object.values(infoModal.clientSettings)[2] ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr><th>{{ i18n "usage" }}</th><th>{{ i18n "pages.inbounds.totalFlow" }}</th><th>{{ i18n "pages.inbounds.expireDate" }}</th><th>{{ i18n "enable" }}</th></tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a-tag :color="statsColor(infoModal.clientStats)">
|
||||||
|
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
||||||
|
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
||||||
|
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
||||||
|
</a-tag>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">[[ sizeFormat(infoModal.clientSettings.totalGB) ]]</a-tag>
|
||||||
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||||
|
<a-tag :color="infoModal.isExpired ? 'red' : 'blue'">
|
||||||
|
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="infoModal.clientStats.enable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
|
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div v-if="dbInbound.hasLink()">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<p>[[ infoModal.link ]]</p>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
||||||
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@@ -11,32 +105,41 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
|
clientSettings: new Inbound.Settings(),
|
||||||
|
clientStats: [],
|
||||||
|
upStats: 0,
|
||||||
|
downStats: 0,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
okBtnPros: {
|
link: null,
|
||||||
attrs: {
|
index: 0,
|
||||||
id: "inbound-info-modal-ok-btn",
|
isExpired: false,
|
||||||
style: "",
|
show(dbInbound, index=0) {
|
||||||
},
|
this.index = index;
|
||||||
},
|
|
||||||
show(dbInbound) {
|
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
|
this.link = dbInbound.genLink(index);
|
||||||
|
this.clientSettings = Object.values(JSON.parse(this.inbound.settings).clients)[index];
|
||||||
|
this.clientStats = dbInbound.clientStats;
|
||||||
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
|
if(dbInbound.clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in dbInbound.clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(dbInbound.clientStats, key)) {
|
||||||
|
if(dbInbound.clientStats[key]['email'] == this.clientSettings.email)
|
||||||
|
this.clientStats = dbInbound.clientStats[key];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
infoModalApp.$nextTick(() => {
|
||||||
if (dbInbound.hasLink()) {
|
if (this.clipboard === null) {
|
||||||
this.okBtnPros.attrs.style = "";
|
this.clipboard = new ClipboardJS('#copy-url-link', {
|
||||||
} else {
|
text: () => this.link,
|
||||||
this.okBtnPros.attrs.style = "display: none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.clipboard == null) {
|
|
||||||
infoModalApp.$nextTick(() => {
|
|
||||||
this.clipboard = new ClipboardJS(`#${this.okBtnPros.attrs.id}`, {
|
|
||||||
text: () => this.dbInbound.genLink(),
|
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copySuccess" }}'));
|
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
@@ -55,6 +158,32 @@
|
|||||||
return this.infoModal.inbound;
|
return this.infoModal.inbound;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
setQrCode(elmentId,index) {
|
||||||
|
content = infoModal.inbound.genLink(infoModal.dbInbound.address,infoModal.dbInbound.remark,index)
|
||||||
|
|
||||||
|
new QRious({
|
||||||
|
element: document.querySelector('#'+elmentId),
|
||||||
|
size: 260,
|
||||||
|
value: content,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
copyTextToClipboard(elmentId,content) {
|
||||||
|
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
|
text: () => content,
|
||||||
|
});
|
||||||
|
this.infoModal.clipboard.on('success', () => {
|
||||||
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
|
this.infoModal.clipboard.destroy();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
statsColor(stats) {
|
||||||
|
if(stats['total'] === 0) return 'blue'
|
||||||
|
else if(stats['total'] > 0 && (stats['down']+stats['up']) < stats['total']) return 'cyan'
|
||||||
|
else return 'red'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -77,41 +77,17 @@
|
|||||||
this.inModal.inbound.tls = false;
|
this.inModal.inbound.tls = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addClient(protocol,value, clients) {
|
addClient(protocol, clients) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
|
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeClient(index, clients) {
|
removeClient(index, clients) {
|
||||||
clients.splice(index, 1);
|
clients.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getDBClientIps(email,event) {
|
|
||||||
|
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ips = JSON.parse(msg.obj)
|
|
||||||
ips = ips.join(",")
|
|
||||||
event.target.value = ips
|
|
||||||
} catch (error) {
|
|
||||||
// text
|
|
||||||
event.target.value = msg.obj
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
async clearDBClientIps(email,event) {
|
|
||||||
const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email);
|
|
||||||
if (!msg.success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.target.value = ""
|
|
||||||
},
|
|
||||||
async resetClientTraffic(client,event) {
|
async resetClientTraffic(client,event) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
@@ -181,6 +157,10 @@
|
|||||||
return email + (this.isClientEnable(email) == true ? ' Active' : ' Deactive')
|
return email + (this.isClientEnable(email) == true ? ' Active' : ' Deactive')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getHeaderStyle(email) {
|
||||||
|
return (this.isClientEnable(email) == true ? '' : 'deactive-client')
|
||||||
|
},
|
||||||
|
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
|
|||||||
@@ -27,15 +27,15 @@
|
|||||||
<a-card hoverable style="margin-bottom: 20px;">
|
<a-card hoverable style="margin-bottom: 20px;">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -46,18 +46,18 @@
|
|||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- <a-input v-model="searchKey" placeholder="搜索" autofocus style="max-width: 300px"></a-input>-->
|
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="dbInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1500 }"
|
:loading="spinning" :scroll="{ x: 1300 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
@change="() => getDBInbounds()">
|
@change="() => getDBInbounds()">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
<a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)">
|
||||||
<a-menu-item v-if="dbInbound.hasLink()" key="qrcode">
|
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -87,9 +87,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="settings" slot-scope="text, dbInbound">
|
|
||||||
<a-button type="link" @click="showInfo(dbInbound)">{{ i18n "check" }}</a-button>
|
|
||||||
</template>
|
|
||||||
<template slot="stream" slot-scope="text, dbInbound, index">
|
<template slot="stream" slot-scope="text, dbInbound, index">
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
|
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
|
||||||
@@ -110,7 +107,36 @@
|
|||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinitely" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
|
<a-table
|
||||||
|
v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
|
||||||
|
:row-key="client => client.id"
|
||||||
|
:columns="innerColumns"
|
||||||
|
:data-source="getInboundClients(record)"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
{{template "client_table"}}
|
||||||
|
</a-table>
|
||||||
|
<a-table
|
||||||
|
v-else-if="record.protocol === Protocols.TROJAN"
|
||||||
|
:row-key="client => client.id"
|
||||||
|
:columns="innerTrojanColumns"
|
||||||
|
:data-source="getInboundClients(record)"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
{{template "client_table"}}
|
||||||
|
</a-table>
|
||||||
|
<a-table
|
||||||
|
v-else
|
||||||
|
:row-key="client => client.id"
|
||||||
|
:columns="innerOneColumns"
|
||||||
|
:data-source="record"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
{{template "client_table"}}
|
||||||
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -140,28 +166,23 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100,
|
width: 60,
|
||||||
dataIndex: "remark",
|
dataIndex: "remark",
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.protocol" }}',
|
title: '{{ i18n "pages.inbounds.protocol" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'protocol' },
|
scopedSlots: { customRender: 'protocol' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.port" }}',
|
title: '{{ i18n "pages.inbounds.port" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: "port",
|
dataIndex: "port",
|
||||||
width: 60,
|
width: 30,
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 150,
|
width: 150,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
|
||||||
title: '{{ i18n "pages.inbounds.details" }}',
|
|
||||||
align: 'center',
|
|
||||||
width: 40,
|
|
||||||
scopedSlots: { customRender: 'settings' },
|
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.transportConfig" }}',
|
title: '{{ i18n "pages.inbounds.transportConfig" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -174,6 +195,26 @@
|
|||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const innerColumns = [
|
||||||
|
{ title: '', width: 20, scopedSlots: { customRender: 'actions' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 80, scopedSlots: { customRender: 'traffic' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
|
{ title: 'UID', width: 150, dataIndex: "id" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const innerTrojanColumns = [
|
||||||
|
{ title: '', width: 20, scopedSlots: { customRender: 'actions' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 80, scopedSlots: { customRender: 'traffic' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
|
{ title: 'Password', width: 150, dataIndex: "password" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const innerOneColumns = [
|
||||||
|
{ title: '', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||||
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -183,6 +224,7 @@
|
|||||||
inbounds: [],
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
searchedInbounds: [],
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning=true) {
|
loading(spinning=true) {
|
||||||
@@ -200,10 +242,12 @@
|
|||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
|
this.searchedInbounds.splice(0);
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
const dbInbound = new DBInbound(inbound);
|
const dbInbound = new DBInbound(inbound);
|
||||||
this.inbounds.push(dbInbound.toInbound());
|
this.inbounds.push(dbInbound.toInbound());
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
|
this.searchedInbounds.push(dbInbound);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
@@ -213,7 +257,18 @@
|
|||||||
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||||
this.dbInbounds.forEach(inbound => {
|
this.dbInbounds.forEach(inbound => {
|
||||||
if (ObjectUtil.deepSearch(inbound, key)) {
|
if (ObjectUtil.deepSearch(inbound, key)) {
|
||||||
this.searchedInbounds.push(inbound);
|
const newInbound = new DBInbound(inbound);
|
||||||
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
|
if (inboundSettings.hasOwnProperty('clients')){
|
||||||
|
const searchedSettings = { "clients": [] };
|
||||||
|
inboundSettings.clients.forEach(client => {
|
||||||
|
if (ObjectUtil.deepSearch(client, key)){
|
||||||
|
searchedSettings.clients.push(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, searchedSettings);
|
||||||
|
}
|
||||||
|
this.searchedInbounds.push(newInbound);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -322,12 +377,12 @@
|
|||||||
onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
|
onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const link = dbInbound.genLink();
|
const link = dbInbound.genLink(clientIndex);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
||||||
},
|
},
|
||||||
showInfo(dbInbound) {
|
showInfo(dbInbound, index) {
|
||||||
infoModal.show(dbInbound);
|
infoModal.show(dbInbound, index);
|
||||||
},
|
},
|
||||||
switchEnable(dbInbound) {
|
switchEnable(dbInbound) {
|
||||||
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
||||||
@@ -338,11 +393,108 @@
|
|||||||
await this.getDBInbounds();
|
await this.getDBInbounds();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getInboundClients(dbInbound) {
|
||||||
|
if(dbInbound.protocol == Protocols.VLESS) {
|
||||||
|
return dbInbound.toInbound().settings.vlesses
|
||||||
|
} else if(dbInbound.protocol == Protocols.VMESS) {
|
||||||
|
return dbInbound.toInbound().settings.vmesses
|
||||||
|
} else if(dbInbound.protocol == Protocols.TROJAN) {
|
||||||
|
return dbInbound.toInbound().settings.trojans
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetClientTraffic(client,inbound,event) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: () => {
|
||||||
|
this.resetClTraffic(client,inbound,event);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async resetClTraffic(client,inbound,event) {
|
||||||
|
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientStats = inbound.clientStats
|
||||||
|
if(clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||||
|
if(clientStats[key]['email'] == client.email){
|
||||||
|
clientStats[key]['up'] = 0
|
||||||
|
clientStats[key]['down'] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isExpiry(dbInbound, index) {
|
||||||
|
return dbInbound.toInbound().isExpiry(index)
|
||||||
|
},
|
||||||
|
getUpStats(dbInbound, email) {
|
||||||
|
clientStats = dbInbound.clientStats
|
||||||
|
if(clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||||
|
if(clientStats[key]['email'] == email)
|
||||||
|
return clientStats[key]['up']
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
getDownStats(dbInbound, email) {
|
||||||
|
clientStats = dbInbound.clientStats
|
||||||
|
if(clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||||
|
if(clientStats[key]['email'] == email)
|
||||||
|
return clientStats[key]['down']
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isTrafficExhausted(dbInbound, email) {
|
||||||
|
clientStats = dbInbound.clientStats
|
||||||
|
if(clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||||
|
if(clientStats[key]['email'] == email)
|
||||||
|
return clientStats[key]['down']+clientStats[key]['up'] > clientStats[key]['total']
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isClientEnabled(dbInbound, email) {
|
||||||
|
clientStats = dbInbound.clientStats
|
||||||
|
if(clientStats.length > 0)
|
||||||
|
{
|
||||||
|
for (const key in clientStats) {
|
||||||
|
if (Object.hasOwnProperty.call(clientStats, key)) {
|
||||||
|
if(clientStats[key]['email'] == email)
|
||||||
|
return clientStats[key]['enable']
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey(value) {
|
searchKey: debounce(function (newVal) {
|
||||||
this.searchInbounds(value);
|
this.searchInbounds(newVal);
|
||||||
}
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getDBInbounds();
|
this.getDBInbounds();
|
||||||
@@ -363,6 +515,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
{{template "promptModal"}}
|
{{template "promptModal"}}
|
||||||
{{template "qrcodeModal"}}
|
{{template "qrcodeModal"}}
|
||||||
|
|||||||
@@ -87,13 +87,28 @@
|
|||||||
style="max-width: 300px"></a-input>
|
style="max-width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<!-- <a-button type="primary" @click="updateUser">修改</a-button>-->
|
<!-- <a-button type="primary" @click="updateUser">Revise</a-button>-->
|
||||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.setting.xrayConfiguration"}}'>
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigTorrent"}}' desc='{{ i18n "pages.setting.xrayConfigTorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigPrivateIp"}}' desc='{{ i18n "pages.setting.xrayConfigPrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||||
|
<a-divider>{{ i18n "pages.setting.advancedTemplate"}}</a-divider>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigInbounds"}}">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigInbounds"}}' desc='{{ i18n "pages.setting.xrayConfigInboundsDesc"}}' v-model ="inboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigOutbounds"}}">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigOutbounds"}}' desc='{{ i18n "pages.setting.xrayConfigOutboundsDesc"}}' v-model ="outboundSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header="{{ i18n "pages.setting.xrayConfigRoutings"}}">
|
||||||
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigRoutings"}}' desc='{{ i18n "pages.setting.xrayConfigRoutingsDesc"}}' v-model ="routingRuleSettings"></setting-list-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -189,6 +204,102 @@
|
|||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
templateSettings: {
|
||||||
|
get: function () { return this.allSetting.xrayTemplateConfig ? JSON.parse(this.allSetting.xrayTemplateConfig) : null ; },
|
||||||
|
set: function (newValue) { this.allSetting.xrayTemplateConfig = JSON.stringify(newValue, null, 2) },
|
||||||
|
},
|
||||||
|
inboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.inbounds = JSON.parse(newValue)
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outboundSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.outbounds, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.outbounds = JSON.parse(newValue)
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routingRuleSettings: {
|
||||||
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.routing.rules = JSON.parse(newValue)
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
torrentSettings: {
|
||||||
|
get: function () {
|
||||||
|
torrentFilter = false
|
||||||
|
if(this.templateSettings != null){
|
||||||
|
this.templateSettings.routing.rules.forEach(routingRule => {
|
||||||
|
if(routingRule.hasOwnProperty("protocol")){
|
||||||
|
if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
|
||||||
|
torrentFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return torrentFilter
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
|
||||||
|
if (newValue){
|
||||||
|
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"protocol\": [\"bittorrent\"],\"type\": \"field\"}"))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newTemplateSettings.routing.rules = [];
|
||||||
|
this.templateSettings.routing.rules.forEach(routingRule => {
|
||||||
|
if (routingRule.hasOwnProperty('protocol')){
|
||||||
|
if (routingRule.protocol[0] === "bittorrent" && routingRule.outboundTag == "blocked"){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTemplateSettings.routing.rules.push(routingRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
privateIpSettings: {
|
||||||
|
get: function () {
|
||||||
|
localIpFilter = false
|
||||||
|
if(this.templateSettings != null){
|
||||||
|
this.templateSettings.routing.rules.forEach(routingRule => {
|
||||||
|
if(routingRule.hasOwnProperty("ip")){
|
||||||
|
if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){
|
||||||
|
localIpFilter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return localIpFilter
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = JSON.parse(this.allSetting.xrayTemplateConfig);
|
||||||
|
if (newValue){
|
||||||
|
newTemplateSettings.routing.rules.push(JSON.parse("{\"outboundTag\": \"blocked\",\"ip\": [\"geoip:private\"],\"type\": \"field\"}"))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newTemplateSettings.routing.rules = [];
|
||||||
|
this.templateSettings.routing.rules.forEach(routingRule => {
|
||||||
|
if (routingRule.hasOwnProperty('ip')){
|
||||||
|
if (routingRule.ip[0] === "geoip:private" && routingRule.outboundTag == "blocked"){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTemplateSettings.routing.rules.push(routingRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,351 +0,0 @@
|
|||||||
package job
|
|
||||||
|
|
||||||
import (
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"os"
|
|
||||||
ss "strings"
|
|
||||||
"regexp"
|
|
||||||
"encoding/json"
|
|
||||||
// "strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"net"
|
|
||||||
"github.com/go-cmd/cmd"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
|
||||||
xrayService service.XrayService
|
|
||||||
inboundService service.InboundService
|
|
||||||
}
|
|
||||||
var job *CheckClientIpJob
|
|
||||||
var disAllowedIps []string
|
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
|
||||||
job = new(CheckClientIpJob)
|
|
||||||
return job
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
|
||||||
logger.Debug("Check Client IP Job...")
|
|
||||||
processLogFile()
|
|
||||||
|
|
||||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
|
||||||
blockedIps := []byte(ss.Join(disAllowedIps,","))
|
|
||||||
err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func processLogFile() {
|
|
||||||
accessLogPath := GetAccessLogPath()
|
|
||||||
if(accessLogPath == "") {
|
|
||||||
logger.Warning("xray log not init in config.json")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
|
||||||
InboundClientIps := make(map[string][]string)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
// clean log
|
|
||||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
|
||||||
checkError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := ss.Split(string(data), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
|
||||||
|
|
||||||
matchesIp := ipRegx.FindString(line)
|
|
||||||
if(len(matchesIp) > 0) {
|
|
||||||
ip := string(matchesIp)
|
|
||||||
if( ip == "127.0.0.1" || ip == "1.1.1.1") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matchesEmail := emailRegx.FindString(line)
|
|
||||||
if(matchesEmail == "") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchesEmail = ss.Split(matchesEmail, "email: ")[1]
|
|
||||||
|
|
||||||
if(InboundClientIps[matchesEmail] != nil) {
|
|
||||||
if(contains(InboundClientIps[matchesEmail],ip)){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}else{
|
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
disAllowedIps = []string{}
|
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
|
||||||
inboundClientIps,err := GetInboundClientIps(clientEmail)
|
|
||||||
sort.Sort(sort.StringSlice(ips))
|
|
||||||
if(err != nil){
|
|
||||||
addInboundClientIps(clientEmail,ips)
|
|
||||||
|
|
||||||
}else{
|
|
||||||
updateInboundClientIps(inboundClientIps,clientEmail,ips)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
|
||||||
LimitDevice := func() { LimitDevice() }
|
|
||||||
|
|
||||||
stop := schedule(LimitDevice, 1000 *time.Millisecond)
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
stop <- true
|
|
||||||
|
|
||||||
}
|
|
||||||
func GetAccessLogPath() string {
|
|
||||||
|
|
||||||
config, err := os.ReadFile("bin/config.json")
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
jsonConfig := map[string]interface{}{}
|
|
||||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
|
||||||
checkError(err)
|
|
||||||
if(jsonConfig["log"] != nil) {
|
|
||||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
|
||||||
if(jsonLog["access"] != nil) {
|
|
||||||
|
|
||||||
accessLogPath := jsonLog["access"].(string)
|
|
||||||
|
|
||||||
return accessLogPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
|
|
||||||
}
|
|
||||||
func checkError(e error) {
|
|
||||||
if e != nil {
|
|
||||||
logger.Warning("client ip job err:", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func contains(s []string, str string) bool {
|
|
||||||
for _, v := range s {
|
|
||||||
if v == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
|
||||||
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return InboundClientIps, nil
|
|
||||||
}
|
|
||||||
func addInboundClientIps(clientEmail string,ips []string) error {
|
|
||||||
inboundClientIps := &model.InboundClientIps{}
|
|
||||||
jsonIps, err := json.Marshal(ips)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
|
||||||
|
|
||||||
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
tx.Commit()
|
|
||||||
} else {
|
|
||||||
tx.Rollback()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = tx.Save(inboundClientIps).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail string,ips []string) error {
|
|
||||||
|
|
||||||
jsonIps, err := json.Marshal(ips)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
|
||||||
|
|
||||||
// check inbound limitation
|
|
||||||
inbound, err := GetInboundByEmail(clientEmail)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
if inbound.Settings == "" {
|
|
||||||
logger.Debug("wrong data ",inbound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := map[string][]model.Client{}
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
|
||||||
clients := settings["clients"]
|
|
||||||
|
|
||||||
for _, client := range clients {
|
|
||||||
if client.Email == clientEmail {
|
|
||||||
|
|
||||||
limitIp := client.LimitIP
|
|
||||||
|
|
||||||
if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) {
|
|
||||||
|
|
||||||
disAllowedIps = append(disAllowedIps,ips[limitIp:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("disAllowedIps ",disAllowedIps)
|
|
||||||
sort.Sort(sort.StringSlice(disAllowedIps))
|
|
||||||
|
|
||||||
db := database.GetDB()
|
|
||||||
err = db.Save(inboundClientIps).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func DisableInbound(id int) error{
|
|
||||||
db := database.GetDB()
|
|
||||||
result := db.Model(model.Inbound{}).
|
|
||||||
Where("id = ? and enable = ?", id, true).
|
|
||||||
Update("enable", false)
|
|
||||||
err := result.Error
|
|
||||||
logger.Warning("disable inbound with id:",id)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
job.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var inbounds *model.Inbound
|
|
||||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%" + clientEmail + "%").Find(&inbounds).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return inbounds, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func LimitDevice(){
|
|
||||||
|
|
||||||
localIp,err := LocalIP()
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head")
|
|
||||||
|
|
||||||
<-c.Start()
|
|
||||||
if len(c.Status().Stdout) > 0 {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
|
||||||
|
|
||||||
data := strings.Split(row," ")
|
|
||||||
|
|
||||||
destIp,destPort,srcIp,srcPort := "","","",""
|
|
||||||
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
|
||||||
|
|
||||||
destPort = portRegx.FindString(data[0])
|
|
||||||
destPort = strings.Replace(destPort,":","",-1)
|
|
||||||
|
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
|
||||||
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
|
||||||
srcPort = strings.Replace(srcPort,":","",-1)
|
|
||||||
|
|
||||||
if(contains(disAllowedIps,srcIp)){
|
|
||||||
dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort)
|
|
||||||
dropCmd.Start()
|
|
||||||
|
|
||||||
logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
|
||||||
// get machine ips
|
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
ips := []string{}
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, i := range ifaces {
|
|
||||||
addrs, err := i.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
ips = append(ips,ip.String())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("System IPs : ",ips)
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func IPsToRegex(ips []string) (string){
|
|
||||||
|
|
||||||
regx := ""
|
|
||||||
for _, ip := range ips {
|
|
||||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
|
||||||
|
|
||||||
}
|
|
||||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
|
||||||
|
|
||||||
return regx
|
|
||||||
}
|
|
||||||
|
|
||||||
func schedule(LimitDevice func(), delay time.Duration) chan bool {
|
|
||||||
stop := make(chan bool)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
LimitDevice()
|
|
||||||
select {
|
|
||||||
case <-time.After(delay):
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"encoding/json"
|
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
"x-ui/logger"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -64,11 +64,11 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
|||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailsExist(emails map[string] bool, ignoreId int) (string, error) {
|
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS})
|
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
||||||
if (ignoreId > 0) {
|
if ignoreId > 0 {
|
||||||
db = db.Where("id != ?", ignoreId)
|
db = db.Where("id != ?", ignoreId)
|
||||||
}
|
}
|
||||||
db = db.Find(&inbounds)
|
db = db.Find(&inbounds)
|
||||||
@@ -96,25 +96,25 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
emails := make(map[string] bool)
|
emails := make(map[string]bool)
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if (client.Email != "") {
|
if client.Email != "" {
|
||||||
if emails[client.Email] {
|
if emails[client.Email] {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
emails[client.Email] = true;
|
emails[client.Email] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.checkEmailsExist(emails, inbound.Id)
|
return s.checkEmailsExist(emails, inbound.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound,error) {
|
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
||||||
exist, err := s.checkPortExist(inbound.Port, 0)
|
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
if exist {
|
if exist {
|
||||||
return inbound, common.NewError("端口已存在:", inbound.Port)
|
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||||
@@ -129,7 +129,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound,erro
|
|||||||
|
|
||||||
err = db.Save(inbound).Error
|
err = db.Save(inbound).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.UpdateClientStat(inbound.Id,inbound.Settings)
|
s.UpdateClientStat(inbound.Id, inbound.Settings)
|
||||||
}
|
}
|
||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if exist {
|
if exist {
|
||||||
return common.NewError("端口已存在:", inbound.Port)
|
return common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
if exist {
|
if exist {
|
||||||
return inbound, common.NewError("端口已存在:", inbound.Port)
|
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||||
@@ -216,7 +216,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
|||||||
oldInbound.Sniffing = inbound.Sniffing
|
oldInbound.Sniffing = inbound.Sniffing
|
||||||
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
|
|
||||||
s.UpdateClientStat(inbound.Id,inbound.Settings)
|
s.UpdateClientStat(inbound.Id, inbound.Settings)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return inbound, db.Save(oldInbound).Error
|
return inbound, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
@@ -276,13 +276,13 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
|
|
||||||
err := txInbound.Where("settings like ?", "%" + traffic.Email + "%").First(inbound).Error
|
err := txInbound.Where("settings like ?", "%"+traffic.Email+"%").First(inbound).Error
|
||||||
traffic.InboundId = inbound.Id
|
traffic.InboundId = inbound.Id
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
// delete removed client record
|
// delete removed client record
|
||||||
clientErr := s.DelClientStat(tx, traffic.Email)
|
clientErr := s.DelClientStat(tx, traffic.Email)
|
||||||
logger.Warning(err, traffic.Email,clientErr)
|
logger.Warning(err, traffic.Email, clientErr)
|
||||||
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -298,11 +298,11 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tx.Where("inbound_id = ?", inbound.Id).Where("email = ?", traffic.Email).
|
if tx.Where("inbound_id = ?", inbound.Id).Where("email = ?", traffic.Email).
|
||||||
UpdateColumn("enable", true).
|
UpdateColumn("enable", true).
|
||||||
UpdateColumn("expiry_time", traffic.ExpiryTime).
|
UpdateColumn("expiry_time", traffic.ExpiryTime).
|
||||||
UpdateColumn("total",traffic.Total).
|
UpdateColumn("total", traffic.Total).
|
||||||
UpdateColumn("up", gorm.Expr("up + ?", traffic.Up)).
|
UpdateColumn("up", gorm.Expr("up + ?", traffic.Up)).
|
||||||
UpdateColumn("down", gorm.Expr("down + ?", traffic.Down)).RowsAffected == 0 {
|
UpdateColumn("down", gorm.Expr("down + ?", traffic.Down)).RowsAffected == 0 {
|
||||||
err = tx.Create(traffic).Error
|
err = tx.Create(traffic).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) (error) {
|
func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
// get settings clients
|
// get settings clients
|
||||||
@@ -344,8 +344,8 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string)
|
|||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("inbound_id = ? and email = ?", inboundId, client.Email).
|
Where("inbound_id = ? and email = ?", inboundId, client.Email).
|
||||||
Updates(map[string]interface{}{"enable": true, "total": client.TotalGB, "expiry_time": client.ExpiryTime})
|
Updates(map[string]interface{}{"enable": true, "total": client.TotalGB, "expiry_time": client.ExpiryTime})
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
clientTraffic := xray.ClientTraffic{}
|
clientTraffic := xray.ClientTraffic{}
|
||||||
clientTraffic.InboundId = inboundId
|
clientTraffic.InboundId = inboundId
|
||||||
@@ -369,30 +369,7 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
|||||||
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
|
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
|
func (s *InboundService) ResetClientTraffic(clientEmail string) error {
|
||||||
db := database.GetDB()
|
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
|
||||||
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return InboundClientIps.Ips, nil
|
|
||||||
}
|
|
||||||
func (s *InboundService) ClearClientIps(clientEmail string) (error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
|
|
||||||
result := db.Model(model.InboundClientIps{}).
|
|
||||||
Where("client_email = ?", clientEmail).
|
|
||||||
Update("ips", "")
|
|
||||||
err := result.Error
|
|
||||||
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *InboundService) ResetClientTraffic(clientEmail string) (error) {
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
@@ -402,7 +379,6 @@ func (s *InboundService) ResetClientTraffic(clientEmail string) (error) {
|
|||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -413,7 +389,7 @@ func (s *InboundService) GetClientTrafficById(uuid string) (traffic *xray.Client
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
traffic = &xray.ClientTraffic{}
|
traffic = &xray.ClientTraffic{}
|
||||||
|
|
||||||
err = db.Model(model.Inbound{}).Where("settings like ?", "%" + uuid + "%").First(inbound).Error
|
err = db.Model(model.Inbound{}).Where("settings like ?", "%"+uuid+"%").First(inbound).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
|
||||||
"github.com/shirou/gopsutil/disk"
|
|
||||||
"github.com/shirou/gopsutil/host"
|
|
||||||
"github.com/shirou/gopsutil/load"
|
|
||||||
"github.com/shirou/gopsutil/mem"
|
|
||||||
"github.com/shirou/gopsutil/net"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -20,6 +14,13 @@ import (
|
|||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/util/sys"
|
"x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/cpu"
|
||||||
|
"github.com/shirou/gopsutil/disk"
|
||||||
|
"github.com/shirou/gopsutil/host"
|
||||||
|
"github.com/shirou/gopsutil/load"
|
||||||
|
"github.com/shirou/gopsutil/mem"
|
||||||
|
"github.com/shirou/gopsutil/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProcessState string
|
type ProcessState string
|
||||||
@@ -171,7 +172,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
url := "https://api.github.com/repos/hossinasaadi/Xray-core/releases"
|
url := "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -214,7 +215,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||||
url := fmt.Sprintf("https://github.com/hossinasaadi/Xray-core/releases/download/%s/%s", version, fileName)
|
url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
"timeLocation": "Asia/Shanghai",
|
"timeLocation": "Asia/Tehran",
|
||||||
"tgBotEnable": "false",
|
"tgBotEnable": "false",
|
||||||
"tgBotToken": "",
|
"tgBotToken": "",
|
||||||
"tgBotChatId": "0",
|
"tgBotChatId": "0",
|
||||||
@@ -69,7 +69,7 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
// 有些设置自动生成,不需要返回到前端给用户修改
|
// Some settings are automatically generated, no need to return to the front end to modify the user
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ func (s *XrayService) GetXrayVersion() string {
|
|||||||
}
|
}
|
||||||
return p.GetVersion()
|
return p.GetVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveIndex(s []interface{}, index int) []interface{} {
|
func RemoveIndex(s []interface{}, index int) []interface{} {
|
||||||
return append(s[:index], s[index+1:]...)
|
return append(s[:index], s[index+1:]...)
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
// get settings clients
|
// get settings clients
|
||||||
settings := map[string]interface{}{}
|
settings := map[string]interface{}{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
// check users active or not
|
// check users active or not
|
||||||
|
|
||||||
@@ -89,16 +91,15 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
for index, client := range clients {
|
for index, client := range clients {
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]interface{})
|
||||||
if c["email"] == clientTraffic.Email {
|
if c["email"] == clientTraffic.Email {
|
||||||
if ! clientTraffic.Enable {
|
if !clientTraffic.Enable {
|
||||||
clients = RemoveIndex(clients,index)
|
clients = RemoveIndex(clients, index)
|
||||||
logger.Info("Remove Inbound User",c["email"] ,"due the expire or traffic limit")
|
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
settings["clients"] = clients
|
settings["clients"] = clients
|
||||||
modifiedSettings, err := json.Marshal(settings)
|
modifiedSettings, err := json.Marshal(settings)
|
||||||
|
|||||||
@@ -1,46 +1,50 @@
|
|||||||
"username" = "username"
|
"username" = "Username"
|
||||||
"password" = "password"
|
"password" = "Password"
|
||||||
"login" = "login"
|
"login" = "Login"
|
||||||
"confirm" = "confirm"
|
"confirm" = "Confirm"
|
||||||
"cancel" = "cancel"
|
"cancel" = "Cancel"
|
||||||
"close" = "close"
|
"close" = "Close"
|
||||||
"copy" = "copy"
|
"copy" = "Copy"
|
||||||
"copied" = "copied"
|
"copied" = "Copied"
|
||||||
"download" = "download"
|
"download" = "Download"
|
||||||
"remark" = "remark"
|
"remark" = "Remark"
|
||||||
"enable" = "enable"
|
"enable" = "Enable"
|
||||||
"protocol" = "protocol"
|
"protocol" = "Protocol"
|
||||||
|
"search" = "Search"
|
||||||
|
|
||||||
"loading" = "Loading"
|
"loading" = "Loading"
|
||||||
"second" = "second"
|
"second" = "Second"
|
||||||
"minute" = "minute"
|
"minute" = "Minute"
|
||||||
"hour" = "hour"
|
"hour" = "Hour"
|
||||||
"day" = "day"
|
"day" = "Day"
|
||||||
"check" = "check"
|
"check" = "Check"
|
||||||
"indefinitely" = "indefinitely"
|
"indefinite" = "Indefinite"
|
||||||
"unlimited" = "unlimited"
|
"unlimited" = "Unlimited"
|
||||||
"none" = "none"
|
"none" = "None"
|
||||||
"qrCode" = "QR Code"
|
"qrCode" = "QR Code"
|
||||||
"edit" = "edit"
|
"info" = "More information"
|
||||||
"delete" = "delete"
|
"edit" = "Edit"
|
||||||
"reset" = "reset"
|
"delete" = "Delete"
|
||||||
|
"reset" = "Reset"
|
||||||
"copySuccess" = "Copy successfully"
|
"copySuccess" = "Copy successfully"
|
||||||
"sure" = "Sure"
|
"sure" = "Sure"
|
||||||
"encryption" = "encryption"
|
"encryption" = "Encryption"
|
||||||
"transmission" = "transmission"
|
"transmission" = "Transmission"
|
||||||
"host" = "host"
|
"host" = "Host"
|
||||||
"path" = "path"
|
"path" = "Path"
|
||||||
"camouflage" = "camouflage"
|
"camouflage" = "Camouflage"
|
||||||
"turnOn" = "turn on"
|
"enabled" = "Enabled"
|
||||||
"closure" = "closure"
|
"disabled" = "Disabled"
|
||||||
"domainName" = "domain name"
|
"domainName" = "Domain name"
|
||||||
"additional" = "alter"
|
"additional" = "Alter"
|
||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "certificat"
|
"certificate" = "Certificat"
|
||||||
"fail" = "fail"
|
"fail" = "Fail"
|
||||||
"success" = " success"
|
"success" = " Success"
|
||||||
"getVersion" = "get version"
|
"getVersion" = "Get version"
|
||||||
"install" = "install"
|
"install" = "Install"
|
||||||
|
"clients" = "Clients"
|
||||||
|
"usage" = "Usage"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
@@ -55,16 +59,15 @@
|
|||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
"invalidFormData" = "Input Data Format Is Invalid"
|
"invalidFormData" = "Input Data Format Is Invalid"
|
||||||
"emptyUsername" = "please Enter Username"
|
"emptyUsername" = "Please Enter Username"
|
||||||
"emptyPassword" = "please Enter Password"
|
"emptyPassword" = "Please Enter Password"
|
||||||
"wrongUsernameOrPassword" = "invalid username or password"
|
"wrongUsernameOrPassword" = "Invalid username or password"
|
||||||
"successLogin" = "Login"
|
"successLogin" = "Login"
|
||||||
|
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
"title" = "system status"
|
"title" = "System status"
|
||||||
"memory" = "memory"
|
"memory" = "Memory"
|
||||||
"hard" = "hard disk"
|
"hard" = "Hard disk"
|
||||||
"xrayStatus" = "xray Status"
|
"xrayStatus" = "xray Status"
|
||||||
"xraySwitch" = "Switch Version"
|
"xraySwitch" = "Switch Version"
|
||||||
"xraySwitchClick" = "Click on the version you want to switch"
|
"xraySwitchClick" = "Click on the version you want to switch"
|
||||||
@@ -78,27 +81,26 @@
|
|||||||
"downSpeed" = "Total download speed for all network cards"
|
"downSpeed" = "Total download speed for all network cards"
|
||||||
"totalSent" = "Total upload traffic of all network cards since system startup"
|
"totalSent" = "Total upload traffic of all network cards since system startup"
|
||||||
"totalReceive" = "Total download traffic of all network cards since system startup"
|
"totalReceive" = "Total download traffic of all network cards since system startup"
|
||||||
"xraySwitchVersionDialog" = "switch xray version"
|
"xraySwitchVersionDialog" = "Switch xray version"
|
||||||
"xraySwitchVersionDialogDesc" = "whether to switch the xray version to"
|
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
||||||
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
||||||
|
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "Inbounds"
|
"title" = "Inbounds"
|
||||||
"totalDownUp" = "Total uploads/downloads"
|
"totalDownUp" = "Total uploads/downloads"
|
||||||
"totalUsage" = "Total usage"
|
"totalUsage" = "Total usage"
|
||||||
"inboundCount" = "Number of inbound"
|
"inboundCount" = "Number of inbound"
|
||||||
"operate" = "operate"
|
"operate" = "Operate"
|
||||||
"enable" = "enable"
|
"enable" = "Enable"
|
||||||
"remark" = "remark"
|
"remark" = "Remark"
|
||||||
"protocol" = "protocol"
|
"protocol" = "Protocol"
|
||||||
"port" = "port"
|
"port" = "Port"
|
||||||
"traffic" = "traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "details"
|
"details" = "Details"
|
||||||
"transportConfig" = "transport config"
|
"transportConfig" = "Transport config"
|
||||||
"expireDate" = "expire date"
|
"expireDate" = "Expire date"
|
||||||
"resetTraffic" = "reset traffic"
|
"resetTraffic" = "Reset traffic"
|
||||||
"addInbound" = "addInbound"
|
"addInbound" = "Add Inbound"
|
||||||
"addTo" = "Add To"
|
"addTo" = "Add To"
|
||||||
"revise" = "Revise"
|
"revise" = "Revise"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify InBound"
|
||||||
@@ -106,43 +108,44 @@
|
|||||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||||
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
||||||
"copyLink" = "Copy Link"
|
"copyLink" = "Copy Link"
|
||||||
"address" = "address"
|
"address" = "Address"
|
||||||
"network" = "network"
|
"network" = "Network"
|
||||||
"destinationPort" = "destination port"
|
"destinationPort" = "Destination port"
|
||||||
"targetAddress" = "target address"
|
"targetAddress" = "Target address"
|
||||||
"disableInsecureEncryption" = "Disable insecure encryption"
|
"disableInsecureEncryption" = "Disable insecure encryption"
|
||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "means no limit"
|
"meansNoLimit" = "Means no limit"
|
||||||
"totalFlow" = "total flow"
|
"totalFlow" = "Total flow"
|
||||||
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
||||||
"noRecommendKeepDefault" = "There are no special requirements to keep the default"
|
"noRecommendKeepDefault" = "There are no special requirements to keep the default"
|
||||||
"certificatePath" = "certificate file path"
|
"certificatePath" = "Certificate file path"
|
||||||
"certificateContent" = "certificate file content"
|
"certificateContent" = "Certificate file content"
|
||||||
"publicKeyPath" = "public key file path"
|
"publicKeyPath" = "Public key file path"
|
||||||
"publicKeyContent" = "public key content"
|
"publicKeyContent" = "Public key content"
|
||||||
"keyPath" = "key file path"
|
"keyPath" = "Key file path"
|
||||||
"keyContent" = "key content"
|
"keyContent" = "Key content"
|
||||||
|
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||||
|
"client" = "Client"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "Obtain"
|
"obtain" = "Obtain"
|
||||||
|
|
||||||
[pages.inbounds.stream.general]
|
[pages.inbounds.stream.general]
|
||||||
"requestHeader" = "request header"
|
"requestHeader" = "Request header"
|
||||||
"name" = "name"
|
"name" = "Name"
|
||||||
"value" = "value"
|
"value" = "Value"
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
[pages.inbounds.stream.tcp]
|
||||||
"requestVersion" = "request version"
|
"requestVersion" = "Request version"
|
||||||
"requestMethod" = "request method"
|
"requestMethod" = "Request method"
|
||||||
"requestPath" = "request path"
|
"requestPath" = "Request path"
|
||||||
"responseVersion" = "response version"
|
"responseVersion" = "Response version"
|
||||||
"responseStatus" = "response status"
|
"responseStatus" = "Response status"
|
||||||
"responseStatusDescription" = "response status description"
|
"responseStatusDescription" = "Response status description"
|
||||||
"responseHeader" = "response header"
|
"responseHeader" = "Response header"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "encryption"
|
"encryption" = "Encryption"
|
||||||
|
|
||||||
|
|
||||||
[pages.setting]
|
[pages.setting]
|
||||||
"title" = "Setting"
|
"title" = "Setting"
|
||||||
@@ -168,8 +171,20 @@
|
|||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
"newUsername" = "New Username"
|
"newUsername" = "New Username"
|
||||||
"newPassword" = "New Password"
|
"newPassword" = "New Password"
|
||||||
"xrayConfigTemplate" = "xray Configuration Template"
|
"advancedTemplate" = "Advanced template parts"
|
||||||
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect"
|
"completeTemplate" = "Complete template of Xray configuration"
|
||||||
|
"xrayConfigTemplate" = "Xray Configuration Template"
|
||||||
|
"xrayConfigTemplateDesc" = "Generate the final xray configuration file based on this template, restart the panel to take effect."
|
||||||
|
"xrayConfigTorrent" = "Ban bittorrent usage"
|
||||||
|
"xrayConfigTorrentDesc" = "Change the configuration temlate to avoid using bittorrent by users, restart the panel to take effect"
|
||||||
|
"xrayConfigPrivateIp" = "Ban private ip range to connect"
|
||||||
|
"xrayConfigPrivateIpDesc" = "Change the configuration temlate to avoid connecting with private IP ranges, restart the panel to take effect"
|
||||||
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
|
"xrayConfigInboundsDesc" = "Change the configuration temlate to accept special clients, restart the panel to take effect"
|
||||||
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
|
"xrayConfigOutboundsDesc" = "Change the configuration temlate to define outgoing ways for this server, restart the panel to take effect"
|
||||||
|
"xrayConfigRoutings" = "Configuration of Routing rules"
|
||||||
|
"xrayConfigRoutingsDesc" = "Change the configuration temlate to define Routing rules for this server, restart the panel to take effect"
|
||||||
"telegramBotEnable" = "Enable telegram bot"
|
"telegramBotEnable" = "Enable telegram bot"
|
||||||
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
"telegramBotEnableDesc" = "Restart the panel to take effect"
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
@@ -182,8 +197,8 @@
|
|||||||
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
||||||
|
|
||||||
[pages.setting.toasts]
|
[pages.setting.toasts]
|
||||||
"modifySetting" = "modify setting"
|
"modifySetting" = "Modify setting"
|
||||||
"getSetting" = "get setting"
|
"getSetting" = "Get setting"
|
||||||
"modifyUser" = "modify user"
|
"modifyUser" = "Modify user"
|
||||||
"originalUserPassIncorrect" = "The original user name or original password is incorrect"
|
"originalUserPassIncorrect" = "The original user name or original password is incorrect"
|
||||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||||
204
web/translation/translate.fa_IR.toml
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
"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" = "استتار"
|
||||||
|
"enabled" = "فعال"
|
||||||
|
"disabled" = "غیرفعال"
|
||||||
|
"domainName" = "آدرس دامنه"
|
||||||
|
"additional" = "آی دی جایگزین"
|
||||||
|
"monitor" = "آی پی اتصال"
|
||||||
|
"certificate" = "سرتیفیکیت"
|
||||||
|
"fail" = "خطا"
|
||||||
|
"success" = " موفق"
|
||||||
|
"getVersion" = "دریافت ورژن"
|
||||||
|
"install" = "نصب"
|
||||||
|
"clients" = "کاربران"
|
||||||
|
"usage" = "استفاده"
|
||||||
|
|
||||||
|
[menu]
|
||||||
|
"dashboard" = "وضعیت سیستم"
|
||||||
|
"inbounds" = "سرویس ها"
|
||||||
|
"setting" = "تنظیمات پنل"
|
||||||
|
"logout" = "خروج"
|
||||||
|
"link" = "دیگر"
|
||||||
|
|
||||||
|
[pages.login]
|
||||||
|
"title" = "ورود به سیستم X-UI"
|
||||||
|
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
|
[pages.login.toasts]
|
||||||
|
"invalidFormData" = "اطلاعات وارد شده به صورت درست وارد نشده است"
|
||||||
|
"emptyUsername" = "نام کاربری خالی میباشد"
|
||||||
|
"emptyPassword" = "رمز عبور خالی میباشد"
|
||||||
|
"wrongUsernameOrPassword" = "نام کاربری و رمز عبور اشتباه میباشد"
|
||||||
|
"successLogin" = "خوش آمدید"
|
||||||
|
|
||||||
|
[pages.index]
|
||||||
|
"title" = "وضعیت سیستم"
|
||||||
|
"memory" = "حافظه رم"
|
||||||
|
"hard" = "حافظه دیسک"
|
||||||
|
"xrayStatus" = "وضعیت Xray"
|
||||||
|
"xraySwitch" = "تغییر ورژن"
|
||||||
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
||||||
|
"operationHours" = "ساعت فعال"
|
||||||
|
"operationHoursDesc" = "ساعت فعال بعد از شروع سیستم"
|
||||||
|
"systemLoad" = "سرعت لود سیستم"
|
||||||
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
|
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
||||||
|
"upSpeed" = "سرعت آپلود در حال حاضر سیستم"
|
||||||
|
"downSpeed" = "سرعت دانلود در حال حاضر سیستم"
|
||||||
|
"totalSent" = "جمع کل ترافیک آپلود مصرفی"
|
||||||
|
"totalReceive" = "جمع کل ترافیک دانلود مصرفی"
|
||||||
|
"xraySwitchVersionDialog" = "تغییر ورژن Xray"
|
||||||
|
"xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین"
|
||||||
|
"dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید "
|
||||||
|
|
||||||
|
[pages.inbounds]
|
||||||
|
"title" = "کاربران"
|
||||||
|
"totalDownUp" = "جمع آپلود/دانلود"
|
||||||
|
"totalUsage" = "جمع کل"
|
||||||
|
"inboundCount" = "تعداد سرویس ها"
|
||||||
|
"operate" = "عملیات"
|
||||||
|
"enable" = "فعال"
|
||||||
|
"remark" = "نام"
|
||||||
|
"protocol" = "پروتکل"
|
||||||
|
"port" = "پورت"
|
||||||
|
"traffic" = "ترافیک"
|
||||||
|
"details" = "توضیحات"
|
||||||
|
"transportConfig" = "نحوه اتصال"
|
||||||
|
"expireDate" = "تاریخ انقضا"
|
||||||
|
"resetTraffic" = "ریست ترافیک"
|
||||||
|
"addInbound" = "اضافه کردن سرویس"
|
||||||
|
"addTo" = "اضافه کردن"
|
||||||
|
"revise" = "ویرایش"
|
||||||
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
|
"deleteInbound" = "حذف سرویس"
|
||||||
|
"deleteInboundContent" = "آیا مطمئن به حذف سرویس هستید ؟"
|
||||||
|
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید ؟"
|
||||||
|
"copyLink" = "کپی لینک"
|
||||||
|
"address" = "آدرس"
|
||||||
|
"network" = "شبکه"
|
||||||
|
"destinationPort" = "پورت مقصد"
|
||||||
|
"targetAddress" = "آدرس مقصد"
|
||||||
|
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید"
|
||||||
|
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||||
|
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||||
|
"totalFlow" = "کل ترافیک"
|
||||||
|
"leaveBlankToNeverExpire" = "خالی بگذارید تا هرگز منقضی نشود"
|
||||||
|
"noRecommendKeepDefault" = "توصیه می شود به عنوان پیش فرض حفظ شود"
|
||||||
|
"certificatePath" = "مسیر فایل گواهی"
|
||||||
|
"certificateContent" = "محتوای فایل گواهی"
|
||||||
|
"publicKeyPath" = "مسیر فایل Certificate.crt"
|
||||||
|
"publicKeyContent" = "محتوای Certificate.crt"
|
||||||
|
"keyPath" = "مسیر فایل Private.key"
|
||||||
|
"keyContent" = "محتوای Private.key"
|
||||||
|
"clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید"
|
||||||
|
"client" = "کاربر"
|
||||||
|
|
||||||
|
[pages.inbounds.toasts]
|
||||||
|
"obtain" = "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.setting]
|
||||||
|
"title" = "تنظیمات"
|
||||||
|
"save" = "ذخیره"
|
||||||
|
"restartPanel" = "ریستارت پنل"
|
||||||
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
|
"panelConfig" = "تنظیمات پنل"
|
||||||
|
"userSetting" = "تنظیمات مدیر"
|
||||||
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
|
"TGReminder" = "تنظیمات ربات تلگرام"
|
||||||
|
"otherSetting" = "دیگر تنظیمات"
|
||||||
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
|
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"panelPort" = "پورت پنل"
|
||||||
|
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"publicKeyPath" = "مسیر فایل پنل Certificate.crt"
|
||||||
|
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"privateKeyPath" = "مسیر فایل پنل private.key"
|
||||||
|
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"panelUrlPath" = "آدرس روت پنل"
|
||||||
|
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"oldUsername" = "نام کاربری فعلی"
|
||||||
|
"currentPassword" = "رمز عبور فعلی"
|
||||||
|
"newUsername" = "نام کاربری جدید"
|
||||||
|
"newPassword" = "رمز عبور جدید"
|
||||||
|
"advancedTemplate" = "بخش های پیشرفته الگو"
|
||||||
|
"completeTemplate" = "الگوی کامل تنظیمات ایکس ری"
|
||||||
|
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
||||||
|
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
||||||
|
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigPrivateIp" = "جلوگیری از اتصال آی پی های نامعتبر"
|
||||||
|
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آی پی های نامعتبر و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigInbounds" = "تنظیمات ورودی"
|
||||||
|
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||||
|
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
||||||
|
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
||||||
|
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramToken" = "توکن تلگرام"
|
||||||
|
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramChatId" = "آی دی تلگرام مدیریت . از ربات @getidsbot آی دی خود را دریافت کنید"
|
||||||
|
"telegramChatIdDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی Crontab استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
|
"timeZonee" = "منظقه زمانی"
|
||||||
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
|
||||||
|
[pages.setting.toasts]
|
||||||
|
"modifySetting" = "ویرایش تنظیمات"
|
||||||
|
"getSetting" = "دریافت تنظیمات"
|
||||||
|
"modifyUser" = "ویرایش کاربر"
|
||||||
|
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
||||||
|
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"enable" = "启用"
|
"enable" = "启用"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
|
"search" = "搜尋"
|
||||||
|
|
||||||
"loading" = "加载中"
|
"loading" = "加载中"
|
||||||
"second" = "秒"
|
"second" = "秒"
|
||||||
@@ -17,10 +18,11 @@
|
|||||||
"hour" = "小时"
|
"hour" = "小时"
|
||||||
"day" = "天"
|
"day" = "天"
|
||||||
"check" = "查看"
|
"check" = "查看"
|
||||||
"indefinitely" = "无限期"
|
"indefinite" = "无限期"
|
||||||
"unlimited" = "无限制"
|
"unlimited" = "无限制"
|
||||||
"none" = "无"
|
"none" = "无"
|
||||||
"qrCode" = "二维码"
|
"qrCode" = "二维码"
|
||||||
|
"info" = "更多信息"
|
||||||
"edit" = "编辑"
|
"edit" = "编辑"
|
||||||
"delete" = "删除"
|
"delete" = "删除"
|
||||||
"reset" = "重置"
|
"reset" = "重置"
|
||||||
@@ -31,8 +33,8 @@
|
|||||||
"host" = "主持人"
|
"host" = "主持人"
|
||||||
"path" = "小路"
|
"path" = "小路"
|
||||||
"camouflage" = "伪装"
|
"camouflage" = "伪装"
|
||||||
"turnOn" = "开启"
|
"enabled" = "开启"
|
||||||
"closure" = "关闭"
|
"disabled" = "关闭"
|
||||||
"domainName" = "域名"
|
"domainName" = "域名"
|
||||||
"additional" = "额外"
|
"additional" = "额外"
|
||||||
"monitor" = "监听"
|
"monitor" = "监听"
|
||||||
@@ -41,6 +43,8 @@
|
|||||||
"success" = "成功"
|
"success" = "成功"
|
||||||
"getVersion" = "获取版本"
|
"getVersion" = "获取版本"
|
||||||
"install" = "安装"
|
"install" = "安装"
|
||||||
|
"clients" = "客户端"
|
||||||
|
"usage" = "用法"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
@@ -81,7 +85,6 @@
|
|||||||
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至"
|
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至"
|
||||||
"dontRefreshh" = "安装中,请不要刷新此页面"
|
"dontRefreshh" = "安装中,请不要刷新此页面"
|
||||||
|
|
||||||
|
|
||||||
[pages.inbounds]
|
[pages.inbounds]
|
||||||
"title" = "入站列表"
|
"title" = "入站列表"
|
||||||
"totalDownUp" = "总上传 / 下载"
|
"totalDownUp" = "总上传 / 下载"
|
||||||
@@ -121,6 +124,8 @@
|
|||||||
"publicKeyContent" = "公钥内容"
|
"publicKeyContent" = "公钥内容"
|
||||||
"keyPath" = "密钥文件路径"
|
"keyPath" = "密钥文件路径"
|
||||||
"keyContent" = "密钥内容"
|
"keyContent" = "密钥内容"
|
||||||
|
"clickOnQRcode" = "点击二维码复制"
|
||||||
|
"client" = "客户"
|
||||||
|
|
||||||
[pages.inbounds.toasts]
|
[pages.inbounds.toasts]
|
||||||
"obtain" = "获取"
|
"obtain" = "获取"
|
||||||
@@ -142,7 +147,6 @@
|
|||||||
[pages.inbounds.stream.quic]
|
[pages.inbounds.stream.quic]
|
||||||
"encryption" = "加密"
|
"encryption" = "加密"
|
||||||
|
|
||||||
|
|
||||||
[pages.setting]
|
[pages.setting]
|
||||||
"title" = "设置"
|
"title" = "设置"
|
||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
@@ -167,8 +171,20 @@
|
|||||||
"currentPassword" = "原密码"
|
"currentPassword" = "原密码"
|
||||||
"newUsername" = "新用户名"
|
"newUsername" = "新用户名"
|
||||||
"newPassword" = "新密码"
|
"newPassword" = "新密码"
|
||||||
"xrayConfigTemplate" = "xray 配置模版"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"xrayConfigTemplateDesc" = "以该模版为基础生成最终的 xray 配置文件,重启面板生效"
|
"completeTemplate" = "Xray 配置的完整模板"
|
||||||
|
"xrayConfigTemplate" = "xray 配置模板"
|
||||||
|
"xrayConfigTemplateDesc" = "以该模型为基础生成最终的xray配置文件,重新启动面板生成效率"
|
||||||
|
"xrayConfigTorrent" = "禁止使用 bittorrent"
|
||||||
|
"xrayConfigTorrentDesc" = "更改配置模板避免用户使用bittorrent,重启面板生效"
|
||||||
|
"xrayConfigPrivateIp" = "禁止私人 ip 范围连接"
|
||||||
|
"xrayConfigPrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围,重启面板生效"
|
||||||
|
"xrayConfigInbounds" = "入站配置"
|
||||||
|
"xrayConfigInboundsDesc" = "更改配置模板接受特殊客户端,重启面板生效"
|
||||||
|
"xrayConfigOutbounds" = "出站配置"
|
||||||
|
"xrayConfigOutboundsDesc" = "更改配置模板定义此服务器的传出方式,重启面板生效"
|
||||||
|
"xrayConfigRoutings" = "路由规则配置"
|
||||||
|
"xrayConfigRoutingsDesc" = "更改配置模板为该服务器定义路由规则,重启面板生效"
|
||||||
"telegramBotEnable" = "启用电报机器人"
|
"telegramBotEnable" = "启用电报机器人"
|
||||||
"telegramBotEnableDesc" = "重启面板生效"
|
"telegramBotEnableDesc" = "重启面板生效"
|
||||||
"telegramToken" = "电报机器人TOKEN"
|
"telegramToken" = "电报机器人TOKEN"
|
||||||
@@ -186,4 +202,3 @@
|
|||||||
"modifyUser" = "修改用户"
|
"modifyUser" = "修改用户"
|
||||||
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
"originalUserPassIncorrect" = "原用户名或原密码错误"
|
||||||
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
"username" = "用戶名"
|
|
||||||
"password" = "密碼"
|
|
||||||
"login" = "登錄"
|
|
||||||
"confirm" = "確定"
|
|
||||||
"cancel" = "取消"
|
|
||||||
"close" = "關閉"
|
|
||||||
"copy" = "複製"
|
|
||||||
"copied" = "已複製"
|
|
||||||
"download" = "下載"
|
|
||||||
"remark" = "備註"
|
|
||||||
"enable" = "啟用"
|
|
||||||
"protocol" = "協議"
|
|
||||||
|
|
||||||
[menu]
|
|
||||||
"dashboard" = "系统状态"
|
|
||||||
"inbounds" = "入站列表"
|
|
||||||
"setting" = "面板设置"
|
|
||||||
"logout" = "退出登录"
|
|
||||||
"link" = "其他"
|
|
||||||
|
|
||||||
[pages.login]
|
|
||||||
"title" = "登錄"
|
|
||||||
|
|
||||||
[pages.login.toasts]
|
|
||||||
"invalidFormData" = "数据格式错误"
|
|
||||||
"emptyUsername" = "请输入用户名"
|
|
||||||
"emptyPassword" = "请输入密码"
|
|
||||||
"wrongUsernameOrPassword" = "用户名或密码错误"
|
|
||||||
"successLogin" = "登录"
|
|
||||||
|
|
||||||
[pages.index]
|
|
||||||
"title" = "系统状态"
|
|
||||||
|
|
||||||
[pages.inbounds]
|
|
||||||
"title" = "入站列表"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.general]
|
|
||||||
"requestHeader" = "request header"
|
|
||||||
"name" = "name"
|
|
||||||
"value" = "value"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.tcp]
|
|
||||||
"requestVersion" = "request version"
|
|
||||||
"requestMethod" = "request method"
|
|
||||||
"requestPath" = "request path"
|
|
||||||
"responseVersion" = "response version"
|
|
||||||
"responseStatus" = "response status"
|
|
||||||
"responseStatusDescription" = "response status description"
|
|
||||||
"responseHeader" = "response header"
|
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "encryption"
|
|
||||||
|
|
||||||
[pages.setting]
|
|
||||||
"title" = "设置"
|
|
||||||
@@ -271,7 +271,7 @@ func (s *Server) initI18n(engine *gin.Engine) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.FuncMap["i18n"] = I18n;
|
engine.FuncMap["i18n"] = I18n
|
||||||
|
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
//accept := c.GetHeader("Accept-Language")
|
//accept := c.GetHeader("Accept-Language")
|
||||||
@@ -286,7 +286,7 @@ func (s *Server) initI18n(engine *gin.Engine) error {
|
|||||||
|
|
||||||
localizer = i18n.NewLocalizer(bundle, lang)
|
localizer = i18n.NewLocalizer(bundle, lang)
|
||||||
c.Set("localizer", localizer)
|
c.Set("localizer", localizer)
|
||||||
c.Set("I18n" , I18n)
|
c.Set("I18n", I18n)
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -310,9 +310,6 @@ func (s *Server) startTask() {
|
|||||||
// 每 30 秒检查一次 inbound 流量超出和到期的情况
|
// 每 30 秒检查一次 inbound 流量超出和到期的情况
|
||||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
||||||
|
|
||||||
// check client ips from log file every 10 sec
|
|
||||||
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
|
|
||||||
|
|
||||||
// 每一天提示一次流量情况,上海时间8点30
|
// 每一天提示一次流量情况,上海时间8点30
|
||||||
var entry cron.EntryID
|
var entry cron.EntryID
|
||||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Environment="XRAY_VMESS_AEAD_FORCED=false"
|
|||||||
Type=simple
|
Type=simple
|
||||||
WorkingDirectory=/usr/local/x-ui/
|
WorkingDirectory=/usr/local/x-ui/
|
||||||
ExecStart=/usr/local/x-ui/x-ui
|
ExecStart=/usr/local/x-ui/x-ui
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
6
x-ui.sh
@@ -94,7 +94,7 @@ before_show_menu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/hossinasaadi/x-ui/main/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/main/install.sh)
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
start
|
start
|
||||||
@@ -113,7 +113,7 @@ update() {
|
|||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/hossinasaadi/x-ui/main/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/main/install.sh)
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "Update is complete, Panel has automatically restarted "
|
LOGI "Update is complete, Panel has automatically restarted "
|
||||||
exit 0
|
exit 0
|
||||||
@@ -302,7 +302,7 @@ install_bbr() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_shell() {
|
update_shell() {
|
||||||
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/hossinasaadi/x-ui/raw/main/x-ui.sh
|
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/alireza0/x-ui/raw/main/x-ui.sh
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGE "Failed to download script,Please check whether the machine can connect Github"
|
LOGE "Failed to download script,Please check whether the machine can connect Github"
|
||||||
|
|||||||
@@ -154,15 +154,15 @@ func (p *process) Start() (err error) {
|
|||||||
|
|
||||||
data, err := json.MarshalIndent(p.config, "", " ")
|
data, err := json.MarshalIndent(p.config, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewErrorf("生成 xray 配置文件失败: %v", err)
|
return common.NewErrorf("Failure to generate XRAY configuration files: %v", err)
|
||||||
}
|
}
|
||||||
configPath := GetConfigPath()
|
configPath := GetConfigPath()
|
||||||
err = os.WriteFile(configPath, data, fs.ModePerm)
|
err = os.WriteFile(configPath, data, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewErrorf("写入配置文件失败: %v", err)
|
return common.NewErrorf("Write the configuration file failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", "./bin/blockedIPs")
|
cmd := exec.Command(GetBinaryPath(), "-c", configPath)
|
||||||
p.cmd = cmd
|
p.cmd = cmd
|
||||||
|
|
||||||
stdReader, err := cmd.StdoutPipe()
|
stdReader, err := cmd.StdoutPipe()
|
||||||
@@ -262,18 +262,18 @@ func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
|||||||
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
||||||
if len(matchs) < 3 {
|
if len(matchs) < 3 {
|
||||||
continue
|
continue
|
||||||
}else {
|
} else {
|
||||||
|
|
||||||
isUser := matchs[1] == "user"
|
isUser := matchs[1] == "user"
|
||||||
email := matchs[2]
|
email := matchs[2]
|
||||||
isDown := matchs[3] == "downlink"
|
isDown := matchs[3] == "downlink"
|
||||||
if ! isUser {
|
if !isUser {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
traffic, ok := emailTrafficMap[email]
|
traffic, ok := emailTrafficMap[email]
|
||||||
if !ok {
|
if !ok {
|
||||||
traffic = &ClientTraffic{
|
traffic = &ClientTraffic{
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
emailTrafficMap[email] = traffic
|
emailTrafficMap[email] = traffic
|
||||||
clientTraffics = append(clientTraffics, traffic)
|
clientTraffics = append(clientTraffics, traffic)
|
||||||
|
|||||||