Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
2
.github/workflows/docker.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
project:
|
||||
description: 'Project'
|
||||
required: true
|
||||
default:
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
13
.github/workflows/release.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-18.04
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: '1.20'
|
||||
- name: build linux amd64 version
|
||||
run: |
|
||||
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
|
||||
mkdir 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
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
@@ -69,7 +70,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: '1.20'
|
||||
- name: build linux arm64 version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -83,7 +84,7 @@ jobs:
|
||||
mv xui-release x-ui
|
||||
mkdir 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
|
||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
@@ -111,7 +112,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: '1.20'
|
||||
- name: build linux s390x version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -125,7 +126,7 @@ jobs:
|
||||
mv xui-release x-ui
|
||||
mkdir 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
|
||||
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
|
||||
4
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
.idea
|
||||
.vscode
|
||||
tmp
|
||||
bin/xray-darwin-arm64
|
||||
bin/config.json
|
||||
bin/
|
||||
dist/
|
||||
x-ui-*.tar.gz
|
||||
/x-ui
|
||||
|
||||
23
Dockerfile
@@ -1,17 +1,16 @@
|
||||
FROM golang:latest AS builder
|
||||
WORKDIR /root
|
||||
FROM golang:1.20-alpine AS builder
|
||||
WORKDIR /app
|
||||
ENV CGO_ENABLED 1
|
||||
RUN apk add gcc && apk --no-cache --update add build-base
|
||||
COPY . .
|
||||
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
|
||||
LABEL org.opencontainers.image.authors="hossin.asaadi77@gmail.com"
|
||||
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/.
|
||||
RUN apk add ca-certificates tzdata && mkdir bin
|
||||
COPY --from=builder /app/main /app/x-ui
|
||||
VOLUME [ "/etc/x-ui" ]
|
||||
CMD [ "./x-ui" ]
|
||||
CMD [ "./x-ui" ]
|
||||
144
README.md
@@ -2,22 +2,23 @@
|
||||
> **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? |
|
||||
| ------------- |:-------------:|
|
||||
| Multi-lang | :heavy_check_mark: |
|
||||
| [IP Restriction](https://github.com/HexaSoftwareTech/x-ui/#enable-ip-restrictions-per-inbound) | :heavy_check_mark: |
|
||||
| [Inbound Multi User](https://github.com/HexaSoftwareTech/x-ui/#enable-multi-user-traffic--exprire-day) | :heavy_check_mark: |
|
||||
| [Multi User Traffic & expire day](https://github.com/HexaSoftwareTech/x-ui/#enable-multi-user-traffic--exprire-day) | :heavy_check_mark: |
|
||||
| [REST API](https://github.com/HexaSoftwareTech/x-ui/pull/51) | :heavy_check_mark: |
|
||||
| [Telegram BOT](https://github.com/HexaSoftwareTech/x-ui/pull/110) | :heavy_check_mark: |
|
||||
| Search in deep | :heavy_check_mark: |
|
||||
| Inbound Multi User | :heavy_check_mark: |
|
||||
| Multi User Traffic & Expiration time | :heavy_check_mark: |
|
||||
| REST API | :heavy_check_mark: |
|
||||
| Telegram BOT | :heavy_check_mark: |
|
||||
|
||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||
|
||||
# Features
|
||||
|
||||
- System Status Monitoring
|
||||
- Search within all inbounds and clients
|
||||
- Support multi-user multi-protocol, web page visualization operation
|
||||
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
||||
- Support for configuring more transport configurations
|
||||
@@ -27,70 +28,19 @@ xray panel supporting multi-protocol, **Multi-lang (English,Chinese)**, **IP Res
|
||||
- Support one-click SSL certificate application and automatic renewal
|
||||
- For more advanced configuration items, please refer to the panel
|
||||
|
||||
# Enable IP Restrictions Per Inbound
|
||||
`!!! NO NEED TO DO THIS IF YOU HAVE FRESH INSTALL`
|
||||
# Inbouds picture
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
> If your server cpu architecture is not `amd64` replace another architecture
|
||||
@@ -110,7 +60,7 @@ systemctl restart x-ui
|
||||
|
||||
## 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
|
||||
|
||||
@@ -126,7 +76,7 @@ docker run -itd --network=host \
|
||||
-v $PWD/db/:/etc/x-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--name x-ui --restart=unless-stopped \
|
||||
HexaSoftwareTech/x-ui:latest
|
||||
alireza0/x-ui:latest
|
||||
```
|
||||
|
||||
> Build your own image
|
||||
@@ -137,27 +87,19 @@ docker build -t x-ui .
|
||||
|
||||
## SSL certificate application
|
||||
|
||||
### Cloudflare
|
||||
|
||||
> 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
|
||||
- Know the Cloudflare Global API Key
|
||||
- The domain name has been resolved to the current server through cloudflare
|
||||
```bash
|
||||
snap install core; snap refresh core
|
||||
snap install --classic certbot
|
||||
ln -s /snap/bin/certbot /usr/bin/certbot
|
||||
|
||||
How to get the Cloudflare Global API Key:
|
||||

|
||||

|
||||
|
||||
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
|
||||
certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <Your Domain Name>
|
||||
```
|
||||
|
||||
## Tg robot use (under development, temporarily unavailable)
|
||||
|
||||
@@ -205,6 +147,46 @@ First install the latest version of x-ui on the server where v2-ui is installed,
|
||||
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
|
||||
|
||||
[](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.1
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"x-ui/config"
|
||||
"x-ui/xray"
|
||||
"x-ui/database/model"
|
||||
"x-ui/xray"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
@@ -41,9 +42,7 @@ func initInbound() error {
|
||||
func initSetting() error {
|
||||
return db.AutoMigrate(&model.Setting{})
|
||||
}
|
||||
func initInboundClientIps() error {
|
||||
return db.AutoMigrate(&model.InboundClientIps{})
|
||||
}
|
||||
|
||||
func initClientTraffic() error {
|
||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||
}
|
||||
@@ -83,15 +82,12 @@ func InitDB(dbPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = initInboundClientIps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = initClientTraffic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,15 +24,15 @@ type User struct {
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserId int `json:"-"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserId int `json:"-"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
|
||||
// config part
|
||||
Listen string `json:"listen" form:"listen"`
|
||||
@@ -43,11 +43,6 @@ type Inbound struct {
|
||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||
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 {
|
||||
listen := i.Listen
|
||||
@@ -70,12 +65,12 @@ type Setting struct {
|
||||
Key string `json:"key" form:"key"`
|
||||
Value string `json:"value" form:"value"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
ID string `json:"id"`
|
||||
AlterIds uint16 `json:"alterId"`
|
||||
Email string `json:"email"`
|
||||
LimitIP int `json:"limitIp"`
|
||||
Security string `json:"security"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ID string `json:"id"`
|
||||
AlterIds uint16 `json:"alterId"`
|
||||
Email string `json:"email"`
|
||||
Security string `json:"security"`
|
||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
}
|
||||
}
|
||||
|
||||
BIN
db/x-ui.db
Normal file
@@ -1,7 +1,7 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
xui:
|
||||
image: hossinasaadi/x-ui
|
||||
image: alireza7/x-ui
|
||||
container_name: x-ui
|
||||
volumes:
|
||||
- $PWD/db/:/etc/x-ui/
|
||||
|
||||
51
go.mod
@@ -1,27 +1,52 @@
|
||||
module x-ui
|
||||
|
||||
go 1.16
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
||||
github.com/BurntSushi/toml v1.0.0
|
||||
github.com/Workiva/go-datastructures v1.0.53
|
||||
github.com/gin-contrib/sessions v0.0.3
|
||||
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/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/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil v3.21.3+incompatible
|
||||
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||
github.com/xtls/xray-core v1.4.2
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
|
||||
golang.org/x/text v0.3.6
|
||||
google.golang.org/grpc v1.38.0
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
github.com/xtls/xray-core v1.7.5
|
||||
go.uber.org/atomic v1.10.0
|
||||
golang.org/x/text v0.6.0
|
||||
google.golang.org/grpc v1.53.0
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
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/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
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/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
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/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
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.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/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-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-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||
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-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
@@ -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/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/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||
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-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/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||
github.com/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.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/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/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
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/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.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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/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/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/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
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/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.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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
||||
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.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=
|
||||
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=
|
||||
@@ -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-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-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-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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/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.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-20180826012351-8a410e7b638d/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-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-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-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-20181017192945-9dcd33a902f4/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-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-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-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-20180909124046-d0be0721c37e/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-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-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
|
||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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-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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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-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.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-20191011141410-1b5146add898/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-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-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.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
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.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
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 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
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/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/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/yaml.v2 v2.2.1/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
|
||||
release="centos"
|
||||
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
|
||||
|
||||
arch=$(arch)
|
||||
@@ -39,13 +39,13 @@ elif [[ $arch == "s390x" ]]; then
|
||||
arch="s390x"
|
||||
else
|
||||
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
|
||||
|
||||
echo "arch: ${arch}"
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -61,15 +61,15 @@ fi
|
||||
|
||||
if [[ x"${release}" == x"centos" ]]; 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
|
||||
elif [[ x"${release}" == x"ubuntu" ]]; 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
|
||||
elif [[ x"${release}" == x"debian" ]]; 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
|
||||
|
||||
@@ -83,22 +83,22 @@ install_base() {
|
||||
|
||||
#This function will be called when user installed x-ui out of sercurity
|
||||
config_after_install() {
|
||||
echo -e "${yellow} Install/update finished need to modify panel settings out of security ${plain}"
|
||||
read -p "are you continue,if you type n will skip this at this time[y/n]": config_confirm
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||
read -p "please set up your username:" config_account
|
||||
echo -e "${yellow}your username will be:${config_account}${plain}"
|
||||
read -p "please set up your password:" config_password
|
||||
echo -e "${yellow}your password will be:${config_password}${plain}"
|
||||
read -p "please set up the panel port:" config_port
|
||||
echo -e "${yellow}your panel port is:${config_port}${plain}"
|
||||
echo -e "${yellow}initializing,wait some time here...${plain}"
|
||||
read -p "Please set up your username:" config_account
|
||||
echo -e "${yellow}Your username will be:${config_account}${plain}"
|
||||
read -p "Please set up your password:" config_password
|
||||
echo -e "${yellow}Your password will be:${config_password}${plain}"
|
||||
read -p "Please set up the panel port:" config_port
|
||||
echo -e "${yellow}Your panel port is:${config_port}${plain}"
|
||||
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||
/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}
|
||||
echo -e "${yellow}panel port set down!${plain}"
|
||||
echo -e "${yellow}Panel port set successfully!${plain}"
|
||||
else
|
||||
echo -e "${red}Canceled, all setting items are default settings${plain}"
|
||||
echo -e "${red}Canceled, will use the default settings.${plain}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -107,21 +107,21 @@ install_x-ui() {
|
||||
cd /usr/local/
|
||||
|
||||
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
|
||||
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
|
||||
fi
|
||||
echo -e "get x-ui latest version succeed: ${last_version}, begin to install..."
|
||||
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
|
||||
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/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
||||
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
|
||||
fi
|
||||
else
|
||||
last_version=$1
|
||||
url="https://github.com/hossinasaadi/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||
echo -e "begin to install x-ui v$1"
|
||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||
echo -e "Begining to install x-ui v$1"
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
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
|
||||
chmod +x x-ui bin/xray-linux-${arch}
|
||||
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/bin/x-ui
|
||||
config_after_install
|
||||
#echo -e "如果是全新安装,默认网页端口为 ${green}54321${plain},用户名和密码默认都是 ${green}admin${plain}"
|
||||
#echo -e "请自行确保此端口没有被其他程序占用,${yellow}并且确保 54321 端口已放行${plain}"
|
||||
# echo -e "若想将 54321 修改为其它端口,输入 x-ui 命令进行修改,同样也要确保你修改的端口也是放行的"
|
||||
#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 "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 "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 "If it is updated panel, access the panel in your previous way"
|
||||
#echo -e ""
|
||||
systemctl daemon-reload
|
||||
systemctl enable 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 "x-ui control menu usages: "
|
||||
echo -e "----------------------------------------------"
|
||||
@@ -170,6 +170,6 @@ install_x-ui() {
|
||||
echo -e "----------------------------------------------"
|
||||
}
|
||||
|
||||
echo -e "${green}excuting...${plain}"
|
||||
echo -e "${green}Excuting...${plain}"
|
||||
install_base
|
||||
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
@@ -144,4 +144,9 @@
|
||||
|
||||
.ant-progress-inner {
|
||||
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",
|
||||
icon : "🇺🇸"
|
||||
},
|
||||
{
|
||||
name : "Farsi",
|
||||
value : "fa_IR",
|
||||
icon : "🇮🇷"
|
||||
},
|
||||
{
|
||||
name : "汉语",
|
||||
value : "zh-Hans",
|
||||
|
||||
@@ -151,9 +151,9 @@ class DBInbound {
|
||||
}
|
||||
}
|
||||
|
||||
genLink() {
|
||||
genLink(clientIndex) {
|
||||
const inbound = this.toInbound();
|
||||
return inbound.genLink(this.address, this.remark);
|
||||
return inbound.genLink(this.address, this.remark, clientIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -825,6 +825,10 @@ class Inbound extends XrayCommonClass {
|
||||
if(this.settings.vlesses[index]._expiryTime != null)
|
||||
return this.settings.vlesses[index]._expiryTime < new Date().getTime();
|
||||
return false
|
||||
case Protocols.TROJAN:
|
||||
if(this.settings.trojans[index]._expiryTime != null)
|
||||
return this.settings.trojans[index]._expiryTime < new Date().getTime();
|
||||
return false
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -948,7 +952,7 @@ class Inbound extends XrayCommonClass {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let obj = {
|
||||
v: '2',
|
||||
ps: remark,
|
||||
@@ -1053,17 +1057,29 @@ class Inbound extends XrayCommonClass {
|
||||
+ '#' + encodeURIComponent(remark);
|
||||
}
|
||||
|
||||
genTrojanLink(address='', remark='') {
|
||||
genTrojanLink(address='', remark='', clientIndex=0) {
|
||||
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) {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VMESS: return this.genVmessLink(address, remark, clientIndex);
|
||||
case Protocols.VLESS: return this.genVLESSLink(address, remark, clientIndex);
|
||||
case Protocols.VMESS:
|
||||
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.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 '';
|
||||
}
|
||||
}
|
||||
@@ -1181,12 +1197,11 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||
}
|
||||
};
|
||||
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='', totalGB=0, expiryTime='') {
|
||||
super();
|
||||
this.id = id;
|
||||
this.alterId = alterId;
|
||||
this.email = email;
|
||||
this.limitIp = limitIp;
|
||||
this.totalGB = totalGB;
|
||||
this.expiryTime = expiryTime;
|
||||
}
|
||||
@@ -1196,7 +1211,6 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||
json.id,
|
||||
json.alterId,
|
||||
json.email,
|
||||
json.limitIp,
|
||||
json.totalGB,
|
||||
json.expiryTime,
|
||||
|
||||
@@ -1265,12 +1279,11 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||
};
|
||||
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='', totalGB=0, expiryTime='') {
|
||||
super();
|
||||
this.id = id;
|
||||
this.flow = flow;
|
||||
this.email = email;
|
||||
this.limitIp = limitIp;
|
||||
this.totalGB = totalGB;
|
||||
this.expiryTime = expiryTime;
|
||||
|
||||
@@ -1281,7 +1294,6 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
json.id,
|
||||
json.flow,
|
||||
json.email,
|
||||
json.limitIp,
|
||||
json.totalGB,
|
||||
json.expiryTime,
|
||||
|
||||
@@ -1351,10 +1363,10 @@ Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
||||
|
||||
Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||
constructor(protocol,
|
||||
clients=[new Inbound.TrojanSettings.Client()],
|
||||
trojans=[new Inbound.TrojanSettings.Trojan()],
|
||||
fallbacks=[],) {
|
||||
super(protocol);
|
||||
this.clients = clients;
|
||||
this.trojans = trojans;
|
||||
this.fallbacks = fallbacks;
|
||||
}
|
||||
|
||||
@@ -1366,45 +1378,73 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||
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() {
|
||||
return {
|
||||
clients: Inbound.TrojanSettings.toJsonArray(this.clients),
|
||||
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
||||
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 {
|
||||
constructor(password=RandomUtil.randomSeq(10), flow=FLOW_CONTROL.DIRECT) {
|
||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
constructor(password=RandomUtil.randomSeq(10), flow=FLOW_CONTROL.DIRECT, email='', totalGB=0, expiryTime='') {
|
||||
super();
|
||||
this.password = password;
|
||||
this.flow = flow;
|
||||
this.email = email;
|
||||
this.totalGB = totalGB;
|
||||
this.expiryTime = expiryTime;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
password: this.password,
|
||||
flow: this.flow,
|
||||
email: this.email,
|
||||
totalGB: this.totalGB,
|
||||
expiryTime: this.expiryTime,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new Inbound.TrojanSettings.Client(
|
||||
return new Inbound.TrojanSettings.Trojan(
|
||||
json.password,
|
||||
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 {
|
||||
|
||||
@@ -54,4 +54,16 @@ function addZero(num) {
|
||||
function toFixed(num, n) {
|
||||
n = Math.pow(10, 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)
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type InboundController struct {
|
||||
@@ -30,11 +31,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/add", a.addInbound)
|
||||
g.POST("/del/:id", a.delInbound)
|
||||
g.POST("/update/:id", a.updateInbound)
|
||||
|
||||
g.POST("/clientIps/:email", a.getClientIps)
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +52,7 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err)
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, inbounds, nil)
|
||||
@@ -63,12 +60,12 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
||||
func (a *InboundController) getInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "get"), err)
|
||||
jsonMsg(c, I18n(c, "get"), err)
|
||||
return
|
||||
}
|
||||
inbound, err := a.inboundService.GetInbound(id)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "pages.inbounds.toasts.obtain"), err)
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, inbound, nil)
|
||||
@@ -78,7 +75,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "pages.inbounds.addTo"), err)
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.addTo"), err)
|
||||
return
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
@@ -86,7 +83,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
inbound.Enable = true
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
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 {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -95,11 +92,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
func (a *InboundController) delInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "delete"), err)
|
||||
jsonMsg(c, I18n(c, "delete"), err)
|
||||
return
|
||||
}
|
||||
err = a.inboundService.DelInbound(id)
|
||||
jsonMsgObj(c, I18n(c , "delete"), id, err)
|
||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -108,7 +105,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "pages.inbounds.revise"), err)
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
inbound := &model.Inbound{
|
||||
@@ -116,35 +113,16 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
}
|
||||
err = c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c , "pages.inbounds.revise"), err)
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
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) {
|
||||
email := c.Param("email")
|
||||
|
||||
|
||||
@@ -2,20 +2,7 @@
|
||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||
:closable="true" width="300px" :ok-text="qrModal.okText"
|
||||
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 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>
|
||||
<canvas id="qrCode" style="width: 100%; height: 100%;"></canvas>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
@@ -70,50 +57,6 @@
|
||||
data: {
|
||||
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>
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
</a-menu-item>
|
||||
<!--<a-menu-item key="{{ .base_path }}xui/clients">-->
|
||||
<!-- <a-icon type="laptop"></a-icon>-->
|
||||
<!-- <span>客户端</span>-->
|
||||
<!-- <span>Client</span>-->
|
||||
<!--</a-menu-item>-->
|
||||
<a-sub-menu>
|
||||
<template slot="title">
|
||||
<a-icon type="link"></a-icon>
|
||||
<span>{{ i18n "menu.link"}}</span>
|
||||
</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>
|
||||
<span>Github</span>
|
||||
</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"}}
|
||||
<a-form layout="inline">
|
||||
<!-- <a-form-item label="密码认证">-->
|
||||
<!-- <a-form-item label="Password authentication">-->
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
|
||||
@@ -1,15 +1,109 @@
|
||||
{{define "form/trojan"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="inbound.settings.clients[0].password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.xtls" label="flow">
|
||||
<a-select v-model="inbound.settings.clients[0].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>
|
||||
<label>{{ i18n "clients"}} </label>
|
||||
<a-collapse activeKey="0" v-for="(trojan, index) in inbound.settings.trojans"
|
||||
:key="`trojan-${index}`">
|
||||
|
||||
<a-collapse-panel :class="getHeaderStyle(trojan.email)" :header="getHeaderText(trojan.email)">
|
||||
<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-form layout="inline">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
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-item label="fallbacks">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{{define "form/vless"}}
|
||||
<a-form layout="inline">
|
||||
<label>{{ i18n "clients"}}</label>
|
||||
<a-collapse activeKey="0" v-for="(vless, index) in inbound.settings.vlesses"
|
||||
: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-form layout="inline">
|
||||
@@ -22,43 +23,6 @@
|
||||
</span>
|
||||
<a-input v-model.trim="vless.email"></a-input>
|
||||
</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-item label="id">
|
||||
<a-input v-model.trim="vless.id"></a-input>
|
||||
@@ -97,7 +61,7 @@
|
||||
<a-form layout="inline">
|
||||
<a-tooltip v-if="vless._totalGB > 0">
|
||||
<template slot="title">
|
||||
reset traffic
|
||||
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||
</template>
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete" @click="resetClientTraffic(vless,$event)"></a-icon>
|
||||
@@ -105,23 +69,7 @@
|
||||
</a-tooltip>
|
||||
<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>
|
||||
<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">
|
||||
<svg
|
||||
@click="removeClient(index, inbound.settings.vlesses)"
|
||||
@@ -139,9 +87,24 @@
|
||||
</svg>
|
||||
</a-tag>
|
||||
</a-form>
|
||||
|
||||
|
||||
</a-form>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<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-item label="fallbacks">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{{define "form/vmess"}}
|
||||
<a-form layout="inline">
|
||||
<label>{{ i18n "clients"}}</label>
|
||||
<a-collapse activeKey="0" v-for="(vmess, index) in inbound.settings.vmesses"
|
||||
: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-form layout="inline">
|
||||
@@ -21,42 +22,6 @@
|
||||
</span>
|
||||
<a-input v-model.trim="vmess.email"></a-input>
|
||||
</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-item label="id">
|
||||
<a-input v-model.trim="vmess.id"></a-input>
|
||||
@@ -92,7 +57,7 @@
|
||||
<a-form layout="inline">
|
||||
<a-tooltip v-if="vmess._totalGB > 0">
|
||||
<template slot="title">
|
||||
reset traffic
|
||||
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||
</template>
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete" @click="resetClientTraffic(vmess,$event)"></a-icon>
|
||||
@@ -100,26 +65,7 @@
|
||||
</a-tooltip>
|
||||
<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>
|
||||
<!--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">
|
||||
|
||||
<!--Remove Svg Icon-->
|
||||
<svg
|
||||
@click="removeClient(index, inbound.settings.vmesses)"
|
||||
@@ -143,9 +89,23 @@
|
||||
|
||||
</a-collapse>
|
||||
|
||||
|
||||
</a-form>
|
||||
</a-form>
|
||||
<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 layout="inline">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-form-item label="acceptProxyProtocol">
|
||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="http 伪装">
|
||||
<a-form-item label="http {{ i18n "camouflage" }}">
|
||||
<a-switch
|
||||
:checked="inbound.stream.tcp.type === 'http'"
|
||||
@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 v-if="client._totalGB === 0" color="blue">{{ i18n "usage" }}: [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]</a-tag>
|
||||
<a-tag v-if="client._totalGB > 0 && !isTrafficExhausted(record, client.email)" color="green">{{ i18n "usage" }}: [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] / [[client._totalGB]]GB</a-tag>
|
||||
<a-tag v-if="client._totalGB > 0 && isTrafficExhausted(record, client.email)" color="red">{{ i18n "usage" }}: [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] / [[client._totalGB]]GB</a-tag>
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, client, index">
|
||||
<template v-if="client._expiryTime > 0">
|
||||
<a-tag v-if="isExpiry(record, index)" color="red">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</a-tag>
|
||||
<a-tag v-else color="blue">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||
</template>
|
||||
{{end}}
|
||||
@@ -1,9 +1,65 @@
|
||||
{{define "inboundInfoModal"}}
|
||||
{{template "component/inboundInfo"}}
|
||||
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}' @ok="infoModal.ok"
|
||||
:closable="true" :mask-closable="true"
|
||||
ok-text='{{ i18n "pages.inbounds.copyLink"}}' cancel-text='{{ i18n "close" }}' :ok-button-props="infoModal.okBtnPros">
|
||||
<inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
|
||||
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||
:closable="true"
|
||||
:mask-closable="true"
|
||||
:footer="null"
|
||||
>
|
||||
<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>
|
||||
<template v-if="dbInbound.hasLink()">
|
||||
<p>Client URL:</p>
|
||||
<p>[[ infoModal.link ]]</p>
|
||||
<button class="btn" id="copy-url-link"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
@@ -12,31 +68,22 @@
|
||||
inbound: new Inbound(),
|
||||
dbInbound: new DBInbound(),
|
||||
clipboard: null,
|
||||
okBtnPros: {
|
||||
attrs: {
|
||||
id: "inbound-info-modal-ok-btn",
|
||||
style: "",
|
||||
},
|
||||
},
|
||||
show(dbInbound) {
|
||||
link: null,
|
||||
index: 0,
|
||||
show(dbInbound, index=0) {
|
||||
this.index = index;
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.link = dbInbound.genLink(index);
|
||||
this.visible = true;
|
||||
|
||||
if (dbInbound.hasLink()) {
|
||||
this.okBtnPros.attrs.style = "";
|
||||
} else {
|
||||
this.okBtnPros.attrs.style = "display: none";
|
||||
}
|
||||
|
||||
if (this.clipboard == null) {
|
||||
infoModalApp.$nextTick(() => {
|
||||
this.clipboard = new ClipboardJS(`#${this.okBtnPros.attrs.id}`, {
|
||||
text: () => this.dbInbound.genLink(),
|
||||
infoModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#copy-url-link', {
|
||||
text: () => this.link,
|
||||
});
|
||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copySuccess" }}'));
|
||||
});
|
||||
}
|
||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
||||
}
|
||||
});
|
||||
},
|
||||
close() {
|
||||
infoModal.visible = false;
|
||||
@@ -55,6 +102,27 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -77,41 +77,17 @@
|
||||
this.inModal.inbound.tls = false;
|
||||
}
|
||||
},
|
||||
addClient(protocol,value, clients) {
|
||||
addClient(protocol, clients) {
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
removeClient(index, clients) {
|
||||
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) {
|
||||
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
||||
if (!msg.success) {
|
||||
@@ -181,6 +157,10 @@
|
||||
return email + (this.isClientEnable(email) == true ? ' Active' : ' Deactive')
|
||||
},
|
||||
|
||||
getHeaderStyle(email) {
|
||||
return (this.isClientEnable(email) == true ? '' : 'deactive-client')
|
||||
},
|
||||
|
||||
getNewEmail(client) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
|
||||
@@ -27,15 +27,15 @@
|
||||
<a-card hoverable style="margin-bottom: 20px;">
|
||||
<a-row>
|
||||
<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-col>
|
||||
<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-col>
|
||||
<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-col>
|
||||
</a-row>
|
||||
@@ -46,18 +46,18 @@
|
||||
<div slot="title">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound"></a-button>
|
||||
</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"
|
||||
:data-source="dbInbounds"
|
||||
:loading="spinning" :scroll="{ x: 1500 }"
|
||||
:data-source="searchedInbounds"
|
||||
:loading="spinning" :scroll="{ x: 1300 }"
|
||||
:pagination="false"
|
||||
style="margin-top: 20px"
|
||||
@change="() => getDBInbounds()">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
<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-item v-if="dbInbound.hasLink()" key="qrcode">
|
||||
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
</a-menu-item>
|
||||
@@ -87,9 +87,6 @@
|
||||
</template>
|
||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
||||
</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 v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
|
||||
@@ -110,7 +107,36 @@
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</a-tag>
|
||||
</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>
|
||||
</a-table>
|
||||
</a-card>
|
||||
@@ -140,28 +166,23 @@
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
width: 60,
|
||||
dataIndex: "remark",
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.protocol" }}',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'protocol' },
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.port" }}',
|
||||
align: 'center',
|
||||
dataIndex: "port",
|
||||
width: 60,
|
||||
width: 30,
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
scopedSlots: { customRender: 'traffic' },
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.details" }}',
|
||||
align: 'center',
|
||||
width: 40,
|
||||
scopedSlots: { customRender: 'settings' },
|
||||
}, {
|
||||
title: '{{ i18n "pages.inbounds.transportConfig" }}',
|
||||
align: 'center',
|
||||
@@ -174,6 +195,26 @@
|
||||
scopedSlots: { customRender: 'expiryTime' },
|
||||
}];
|
||||
|
||||
const innerColumns = [
|
||||
{ title: '', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 100, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'UID', width: 150, dataIndex: "id" },
|
||||
];
|
||||
|
||||
const innerTrojanColumns = [
|
||||
{ title: '', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 100, scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, scopedSlots: { customRender: 'expiryTime' } },
|
||||
{ title: 'Password', width: 150, dataIndex: "password" },
|
||||
];
|
||||
|
||||
const innerOneColumns = [
|
||||
{ title: '', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#app',
|
||||
@@ -183,6 +224,7 @@
|
||||
inbounds: [],
|
||||
dbInbounds: [],
|
||||
searchKey: '',
|
||||
searchedInbounds: [],
|
||||
},
|
||||
methods: {
|
||||
loading(spinning=true) {
|
||||
@@ -200,10 +242,12 @@
|
||||
setInbounds(dbInbounds) {
|
||||
this.inbounds.splice(0);
|
||||
this.dbInbounds.splice(0);
|
||||
this.searchedInbounds.splice(0);
|
||||
for (const inbound of dbInbounds) {
|
||||
const dbInbound = new DBInbound(inbound);
|
||||
this.inbounds.push(dbInbound.toInbound());
|
||||
this.dbInbounds.push(dbInbound);
|
||||
this.searchedInbounds.push(dbInbound);
|
||||
}
|
||||
},
|
||||
searchInbounds(key) {
|
||||
@@ -322,12 +366,12 @@
|
||||
onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id),
|
||||
});
|
||||
},
|
||||
showQrcode(dbInbound) {
|
||||
const link = dbInbound.genLink();
|
||||
showQrcode(dbInbound, clientIndex) {
|
||||
const link = dbInbound.genLink(clientIndex);
|
||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
||||
},
|
||||
showInfo(dbInbound) {
|
||||
infoModal.show(dbInbound);
|
||||
showInfo(dbInbound, index) {
|
||||
infoModal.show(dbInbound, index);
|
||||
},
|
||||
switchEnable(dbInbound) {
|
||||
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
||||
@@ -338,11 +382,108 @@
|
||||
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: {
|
||||
searchKey(value) {
|
||||
this.searchInbounds(value);
|
||||
}
|
||||
searchKey: debounce(function (newVal) {
|
||||
this.searchInbounds(newVal);
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
this.getDBInbounds();
|
||||
@@ -363,6 +504,7 @@
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{{template "inboundModal"}}
|
||||
{{template "promptModal"}}
|
||||
{{template "qrcodeModal"}}
|
||||
|
||||
@@ -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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
"x-ui/database"
|
||||
"encoding/json"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/xray"
|
||||
"x-ui/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -64,11 +64,11 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
||||
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()
|
||||
var inbounds []*model.Inbound
|
||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS})
|
||||
if (ignoreId > 0) {
|
||||
var inbounds []*model.Inbound
|
||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
||||
if ignoreId > 0 {
|
||||
db = db.Where("id != ?", ignoreId)
|
||||
}
|
||||
db = db.Find(&inbounds)
|
||||
@@ -96,25 +96,25 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
emails := make(map[string] bool)
|
||||
emails := make(map[string]bool)
|
||||
for _, client := range clients {
|
||||
if (client.Email != "") {
|
||||
if client.Email != "" {
|
||||
if emails[client.Email] {
|
||||
return client.Email, nil
|
||||
}
|
||||
emails[client.Email] = true;
|
||||
emails[client.Email] = true
|
||||
}
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return inbound, err
|
||||
}
|
||||
if exist {
|
||||
return inbound, common.NewError("端口已存在:", inbound.Port)
|
||||
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||
}
|
||||
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
@@ -129,7 +129,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound,erro
|
||||
|
||||
err = db.Save(inbound).Error
|
||||
if err == nil {
|
||||
s.UpdateClientStat(inbound.Id,inbound.Settings)
|
||||
s.UpdateClientStat(inbound.Id, inbound.Settings)
|
||||
}
|
||||
return inbound, err
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return common.NewError("端口已存在:", inbound.Port)
|
||||
return common.NewError("Port already exists:", inbound.Port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +187,9 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, err
|
||||
}
|
||||
if exist {
|
||||
return inbound, common.NewError("端口已存在:", inbound.Port)
|
||||
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||
}
|
||||
|
||||
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
if err != nil {
|
||||
return inbound, err
|
||||
@@ -216,7 +216,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
oldInbound.Sniffing = inbound.Sniffing
|
||||
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
|
||||
s.UpdateClientStat(inbound.Id,inbound.Settings)
|
||||
s.UpdateClientStat(inbound.Id, inbound.Settings)
|
||||
db := database.GetDB()
|
||||
return inbound, db.Save(oldInbound).Error
|
||||
}
|
||||
@@ -276,13 +276,13 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
for _, traffic := range traffics {
|
||||
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
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// delete removed client record
|
||||
clientErr := s.DelClientStat(tx, traffic.Email)
|
||||
logger.Warning(err, traffic.Email,clientErr)
|
||||
logger.Warning(err, traffic.Email, clientErr)
|
||||
|
||||
}
|
||||
continue
|
||||
@@ -298,19 +298,19 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
}
|
||||
}
|
||||
if tx.Where("inbound_id = ?", inbound.Id).Where("email = ?", traffic.Email).
|
||||
UpdateColumn("enable", true).
|
||||
UpdateColumn("expiry_time", traffic.ExpiryTime).
|
||||
UpdateColumn("total",traffic.Total).
|
||||
UpdateColumn("up", gorm.Expr("up + ?", traffic.Up)).
|
||||
UpdateColumn("down", gorm.Expr("down + ?", traffic.Down)).RowsAffected == 0 {
|
||||
UpdateColumn("enable", true).
|
||||
UpdateColumn("expiry_time", traffic.ExpiryTime).
|
||||
UpdateColumn("total", traffic.Total).
|
||||
UpdateColumn("up", gorm.Expr("up + ?", traffic.Up)).
|
||||
UpdateColumn("down", gorm.Expr("down + ?", traffic.Down)).RowsAffected == 0 {
|
||||
err = tx.Create(traffic).Error
|
||||
}
|
||||
|
||||
|
||||
if err != nil {
|
||||
logger.Warning("AddClientTraffic update data ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||
count := result.RowsAffected
|
||||
return count, err
|
||||
}
|
||||
func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) (error) {
|
||||
func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) error {
|
||||
db := database.GetDB()
|
||||
|
||||
// get settings clients
|
||||
@@ -344,8 +344,8 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string)
|
||||
clients := settings["clients"]
|
||||
for _, client := range clients {
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
Where("inbound_id = ? and email = ?", inboundId, client.Email).
|
||||
Updates(map[string]interface{}{"enable": true, "total": client.TotalGB, "expiry_time": client.ExpiryTime})
|
||||
Where("inbound_id = ? and email = ?", inboundId, client.Email).
|
||||
Updates(map[string]interface{}{"enable": true, "total": client.TotalGB, "expiry_time": client.ExpiryTime})
|
||||
if result.RowsAffected == 0 {
|
||||
clientTraffic := xray.ClientTraffic{}
|
||||
clientTraffic.InboundId = inboundId
|
||||
@@ -361,7 +361,7 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -369,30 +369,7 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
||||
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) GetInboundClientIps(clientEmail string) (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) {
|
||||
func (s *InboundService) ResetClientTraffic(clientEmail string) error {
|
||||
db := database.GetDB()
|
||||
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
@@ -402,7 +379,6 @@ func (s *InboundService) ResetClientTraffic(clientEmail string) (error) {
|
||||
|
||||
err := result.Error
|
||||
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -413,7 +389,7 @@ func (s *InboundService) GetClientTrafficById(uuid string) (traffic *xray.Client
|
||||
inbound := &model.Inbound{}
|
||||
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 == gorm.ErrRecordNotFound {
|
||||
logger.Warning(err)
|
||||
|
||||
@@ -5,12 +5,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"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/fs"
|
||||
"net/http"
|
||||
@@ -20,6 +14,13 @@ import (
|
||||
"x-ui/logger"
|
||||
"x-ui/util/sys"
|
||||
"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
|
||||
@@ -171,7 +172,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -214,7 +215,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -28,7 +28,7 @@ var defaultValueMap = map[string]string{
|
||||
"webKeyFile": "",
|
||||
"secret": random.Seq(32),
|
||||
"webBasePath": "/",
|
||||
"timeLocation": "Asia/Shanghai",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
"tgBotEnable": "false",
|
||||
"tgBotToken": "",
|
||||
"tgBotChatId": "0",
|
||||
@@ -69,7 +69,7 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||
}
|
||||
|
||||
if !found {
|
||||
// 有些设置自动生成,不需要返回到前端给用户修改
|
||||
// Some settings are automatically generated, no need to return to the front end to modify the user
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,7 @@ func (s *XrayService) GetXrayVersion() string {
|
||||
}
|
||||
return p.GetVersion()
|
||||
}
|
||||
|
||||
func RemoveIndex(s []interface{}, index int) []interface{} {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
||||
@@ -79,25 +81,24 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
// get settings clients
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
// check users active or not
|
||||
|
||||
clientStats := inbound.ClientStats
|
||||
for _, clientTraffic := range clientStats {
|
||||
|
||||
|
||||
for index, client := range clients {
|
||||
c := client.(map[string]interface{})
|
||||
if c["email"] == clientTraffic.Email {
|
||||
if ! clientTraffic.Enable {
|
||||
clients = RemoveIndex(clients,index)
|
||||
logger.Info("Remove Inbound User",c["email"] ,"due the expire or traffic limit")
|
||||
if !clientTraffic.Enable {
|
||||
clients = RemoveIndex(clients, index)
|
||||
logger.Info("Remove Inbound User", c["email"], "due the expire or traffic limit")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
settings["clients"] = clients
|
||||
@@ -105,7 +106,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
inbound.Settings = string(modifiedSettings)
|
||||
}
|
||||
inboundConfig := inbound.GenXrayInboundConfig()
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
"username" = "username"
|
||||
"password" = "password"
|
||||
"login" = "login"
|
||||
"confirm" = "confirm"
|
||||
"cancel" = "cancel"
|
||||
"close" = "close"
|
||||
"copy" = "copy"
|
||||
"copied" = "copied"
|
||||
"download" = "download"
|
||||
"remark" = "remark"
|
||||
"enable" = "enable"
|
||||
"protocol" = "protocol"
|
||||
"username" = "Username"
|
||||
"password" = "Password"
|
||||
"login" = "Login"
|
||||
"confirm" = "Confirm"
|
||||
"cancel" = "Cancel"
|
||||
"close" = "Close"
|
||||
"copy" = "Copy"
|
||||
"copied" = "Copied"
|
||||
"download" = "Download"
|
||||
"remark" = "Remark"
|
||||
"enable" = "Enable"
|
||||
"protocol" = "Protocol"
|
||||
"search" = "Search"
|
||||
|
||||
"loading" = "Loading"
|
||||
"second" = "second"
|
||||
"minute" = "minute"
|
||||
"hour" = "hour"
|
||||
"day" = "day"
|
||||
"check" = "check"
|
||||
"indefinitely" = "indefinitely"
|
||||
"unlimited" = "unlimited"
|
||||
"none" = "none"
|
||||
"second" = "Second"
|
||||
"minute" = "Minute"
|
||||
"hour" = "Hour"
|
||||
"day" = "Day"
|
||||
"check" = "Check"
|
||||
"indefinite" = "Indefinite"
|
||||
"unlimited" = "Unlimited"
|
||||
"none" = "None"
|
||||
"qrCode" = "QR Code"
|
||||
"edit" = "edit"
|
||||
"delete" = "delete"
|
||||
"reset" = "reset"
|
||||
"info" = "More information"
|
||||
"edit" = "Edit"
|
||||
"delete" = "Delete"
|
||||
"reset" = "Reset"
|
||||
"copySuccess" = "Copy successfully"
|
||||
"sure" = "Sure"
|
||||
"encryption" = "encryption"
|
||||
"transmission" = "transmission"
|
||||
"host" = "host"
|
||||
"path" = "path"
|
||||
"camouflage" = "camouflage"
|
||||
"turnOn" = "turn on"
|
||||
"closure" = "closure"
|
||||
"domainName" = "domain name"
|
||||
"additional" = "alter"
|
||||
"encryption" = "Encryption"
|
||||
"transmission" = "Transmission"
|
||||
"host" = "Host"
|
||||
"path" = "Path"
|
||||
"camouflage" = "Camouflage"
|
||||
"enabled" = "Enabled"
|
||||
"disabled" = "Disabled"
|
||||
"domainName" = "Domain name"
|
||||
"additional" = "Alter"
|
||||
"monitor" = "Listen IP"
|
||||
"certificate" = "certificat"
|
||||
"fail" = "fail"
|
||||
"success" = " success"
|
||||
"getVersion" = "get version"
|
||||
"install" = "install"
|
||||
"certificate" = "Certificat"
|
||||
"fail" = "Fail"
|
||||
"success" = " Success"
|
||||
"getVersion" = "Get version"
|
||||
"install" = "Install"
|
||||
"clients" = "Clients"
|
||||
"usage" = "Usage"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "System Status"
|
||||
@@ -55,16 +59,16 @@
|
||||
|
||||
[pages.login.toasts]
|
||||
"invalidFormData" = "Input Data Format Is Invalid"
|
||||
"emptyUsername" = "please Enter Username"
|
||||
"emptyPassword" = "please Enter Password"
|
||||
"wrongUsernameOrPassword" = "invalid username or password"
|
||||
"emptyUsername" = "Please Enter Username"
|
||||
"emptyPassword" = "Please Enter Password"
|
||||
"wrongUsernameOrPassword" = "Invalid username or password"
|
||||
"successLogin" = "Login"
|
||||
|
||||
|
||||
[pages.index]
|
||||
"title" = "system status"
|
||||
"memory" = "memory"
|
||||
"hard" = "hard disk"
|
||||
"title" = "System status"
|
||||
"memory" = "Memory"
|
||||
"hard" = "Hard disk"
|
||||
"xrayStatus" = "xray Status"
|
||||
"xraySwitch" = "Switch Version"
|
||||
"xraySwitchClick" = "Click on the version you want to switch"
|
||||
@@ -78,8 +82,8 @@
|
||||
"downSpeed" = "Total download speed for all network cards"
|
||||
"totalSent" = "Total upload traffic of all network cards since system startup"
|
||||
"totalReceive" = "Total download traffic of all network cards since system startup"
|
||||
"xraySwitchVersionDialog" = "switch xray version"
|
||||
"xraySwitchVersionDialogDesc" = "whether to switch the xray version to"
|
||||
"xraySwitchVersionDialog" = "Switch xray version"
|
||||
"xraySwitchVersionDialogDesc" = "Whether to switch the xray version to"
|
||||
"dontRefreshh" = "Installation is in progress, please do not refresh this page"
|
||||
|
||||
|
||||
@@ -88,17 +92,17 @@
|
||||
"totalDownUp" = "Total uploads/downloads"
|
||||
"totalUsage" = "Total usage"
|
||||
"inboundCount" = "Number of inbound"
|
||||
"operate" = "operate"
|
||||
"enable" = "enable"
|
||||
"remark" = "remark"
|
||||
"protocol" = "protocol"
|
||||
"port" = "port"
|
||||
"traffic" = "traffic"
|
||||
"details" = "details"
|
||||
"transportConfig" = "transport config"
|
||||
"expireDate" = "expire date"
|
||||
"resetTraffic" = "reset traffic"
|
||||
"addInbound" = "addInbound"
|
||||
"operate" = "Operate"
|
||||
"enable" = "Enable"
|
||||
"remark" = "Remark"
|
||||
"protocol" = "Protocol"
|
||||
"port" = "Port"
|
||||
"traffic" = "Traffic"
|
||||
"details" = "Details"
|
||||
"transportConfig" = "Transport config"
|
||||
"expireDate" = "Expire date"
|
||||
"resetTraffic" = "Reset traffic"
|
||||
"addInbound" = "Add Inbound"
|
||||
"addTo" = "Add To"
|
||||
"revise" = "Revise"
|
||||
"modifyInbound" = "Modify InBound"
|
||||
@@ -106,42 +110,44 @@
|
||||
"deleteInboundContent" = "Are you sure you want to delete inbound?"
|
||||
"resetTrafficContent" = "Are you sure you want to reset traffic?"
|
||||
"copyLink" = "Copy Link"
|
||||
"address" = "address"
|
||||
"network" = "network"
|
||||
"destinationPort" = "destination port"
|
||||
"targetAddress" = "target address"
|
||||
"address" = "Address"
|
||||
"network" = "Network"
|
||||
"destinationPort" = "Destination port"
|
||||
"targetAddress" = "Target address"
|
||||
"disableInsecureEncryption" = "Disable insecure encryption"
|
||||
"monitorDesc" = "Leave blank by default"
|
||||
"meansNoLimit" = "means no limit"
|
||||
"totalFlow" = "total flow"
|
||||
"meansNoLimit" = "Means no limit"
|
||||
"totalFlow" = "Total flow"
|
||||
"leaveBlankToNeverExpire" = "Leave blank to never expire"
|
||||
"noRecommendKeepDefault" = "There are no special requirements to keep the default"
|
||||
"certificatePath" = "certificate file path"
|
||||
"certificateContent" = "certificate file content"
|
||||
"publicKeyPath" = "public key file path"
|
||||
"publicKeyContent" = "public key content"
|
||||
"keyPath" = "key file path"
|
||||
"keyContent" = "key content"
|
||||
"certificatePath" = "Certificate file path"
|
||||
"certificateContent" = "Certificate file content"
|
||||
"publicKeyPath" = "Public key file path"
|
||||
"publicKeyContent" = "Public key content"
|
||||
"keyPath" = "Key file path"
|
||||
"keyContent" = "Key content"
|
||||
"clickOnQRcode" = "Click on QR Code to Copy"
|
||||
"client" = "Client"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "Obtain"
|
||||
|
||||
[pages.inbounds.stream.general]
|
||||
"requestHeader" = "request header"
|
||||
"name" = "name"
|
||||
"value" = "value"
|
||||
"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"
|
||||
"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"
|
||||
"encryption" = "Encryption"
|
||||
|
||||
|
||||
[pages.setting]
|
||||
@@ -182,8 +188,8 @@
|
||||
"timeZoneDesc" = "The scheduled task runs according to the time in the time zone, and restarts the panel to take effect"
|
||||
|
||||
[pages.setting.toasts]
|
||||
"modifySetting" = "modify setting"
|
||||
"getSetting" = "get setting"
|
||||
"modifyUser" = "modify user"
|
||||
"modifySetting" = "Modify setting"
|
||||
"getSetting" = "Get setting"
|
||||
"modifyUser" = "Modify user"
|
||||
"originalUserPassIncorrect" = "The original user name or original password is incorrect"
|
||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||
195
web/translation/translate.fa_IR.toml
Normal file
@@ -0,0 +1,195 @@
|
||||
"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" = "رمز عبور جدید"
|
||||
"xrayConfigTemplate" = "تنظیمات قالب Xray"
|
||||
"xrayConfigTemplateDesc" = "فایل پیکربندی xray نهایی را بر اساس این الگو ایجاد کنید. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"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" = "备注"
|
||||
"enable" = "启用"
|
||||
"protocol" = "协议"
|
||||
"search" = "搜尋"
|
||||
|
||||
"loading" = "加载中"
|
||||
"second" = "秒"
|
||||
@@ -17,10 +18,11 @@
|
||||
"hour" = "小时"
|
||||
"day" = "天"
|
||||
"check" = "查看"
|
||||
"indefinitely" = "无限期"
|
||||
"indefinite" = "无限期"
|
||||
"unlimited" = "无限制"
|
||||
"none" = "无"
|
||||
"qrCode" = "二维码"
|
||||
"info" = "更多信息"
|
||||
"edit" = "编辑"
|
||||
"delete" = "删除"
|
||||
"reset" = "重置"
|
||||
@@ -31,8 +33,8 @@
|
||||
"host" = "主持人"
|
||||
"path" = "小路"
|
||||
"camouflage" = "伪装"
|
||||
"turnOn" = "开启"
|
||||
"closure" = "关闭"
|
||||
"enabled" = "开启"
|
||||
"disabled" = "关闭"
|
||||
"domainName" = "域名"
|
||||
"additional" = "额外"
|
||||
"monitor" = "监听"
|
||||
@@ -41,6 +43,7 @@
|
||||
"success" = "成功"
|
||||
"getVersion" = "获取版本"
|
||||
"install" = "安装"
|
||||
"clients" = "客户端"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "系统状态"
|
||||
@@ -121,6 +124,7 @@
|
||||
"publicKeyContent" = "公钥内容"
|
||||
"keyPath" = "密钥文件路径"
|
||||
"keyContent" = "密钥内容"
|
||||
"clickOnQRcode" = "click on QR Code to Copy"
|
||||
|
||||
[pages.inbounds.toasts]
|
||||
"obtain" = "获取"
|
||||
|
||||
@@ -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) {
|
||||
//accept := c.GetHeader("Accept-Language")
|
||||
@@ -286,7 +286,7 @@ func (s *Server) initI18n(engine *gin.Engine) error {
|
||||
|
||||
localizer = i18n.NewLocalizer(bundle, lang)
|
||||
c.Set("localizer", localizer)
|
||||
c.Set("I18n" , I18n)
|
||||
c.Set("I18n", I18n)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
@@ -310,9 +310,6 @@ func (s *Server) startTask() {
|
||||
// 每 30 秒检查一次 inbound 流量超出和到期的情况
|
||||
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
|
||||
var entry cron.EntryID
|
||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||
|
||||
@@ -8,6 +8,7 @@ Environment="XRAY_VMESS_AEAD_FORCED=false"
|
||||
Type=simple
|
||||
WorkingDirectory=/usr/local/x-ui/
|
||||
ExecStart=/usr/local/x-ui/x-ui
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
6
x-ui.sh
@@ -94,7 +94,7 @@ before_show_menu() {
|
||||
}
|
||||
|
||||
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
|
||||
start
|
||||
@@ -113,7 +113,7 @@ update() {
|
||||
fi
|
||||
return 0
|
||||
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
|
||||
LOGI "Update is complete, Panel has automatically restarted "
|
||||
exit 0
|
||||
@@ -302,7 +302,7 @@ install_bbr() {
|
||||
}
|
||||
|
||||
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
|
||||
echo ""
|
||||
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, "", " ")
|
||||
if err != nil {
|
||||
return common.NewErrorf("生成 xray 配置文件失败: %v", err)
|
||||
return common.NewErrorf("Failure to generate XRAY configuration files: %v", err)
|
||||
}
|
||||
configPath := GetConfigPath()
|
||||
err = os.WriteFile(configPath, data, fs.ModePerm)
|
||||
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
|
||||
|
||||
stdReader, err := cmd.StdoutPipe()
|
||||
@@ -262,18 +262,18 @@ func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
||||
if len(matchs) < 3 {
|
||||
continue
|
||||
}else {
|
||||
} else {
|
||||
|
||||
isUser := matchs[1] == "user"
|
||||
email := matchs[2]
|
||||
isDown := matchs[3] == "downlink"
|
||||
if ! isUser {
|
||||
if !isUser {
|
||||
continue
|
||||
}
|
||||
traffic, ok := emailTrafficMap[email]
|
||||
if !ok {
|
||||
traffic = &ClientTraffic{
|
||||
Email: email,
|
||||
Email: email,
|
||||
}
|
||||
emailTrafficMap[email] = traffic
|
||||
clientTraffics = append(clientTraffics, traffic)
|
||||
@@ -283,7 +283,7 @@ func (p *process) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||
} else {
|
||||
traffic.Up = stat.Value
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||