mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c5bb94894 | ||
|
|
39c1a4276d | ||
|
|
4736786c6f | ||
|
|
402c713f06 | ||
|
|
a371bec2aa | ||
|
|
427d008bd1 | ||
|
|
e69d17be67 | ||
|
|
09c61976ea | ||
|
|
6e17c282e0 | ||
|
|
700973655c | ||
|
|
dcb54267f2 | ||
|
|
19e851d5c8 | ||
|
|
3f7ef07b8e | ||
|
|
89be2c8fec | ||
|
|
dd11585074 | ||
|
|
7ecb73af8c | ||
|
|
ba083ecc7e | ||
|
|
365ec1a704 | ||
|
|
63969d5fd6 | ||
|
|
0449a35409 | ||
|
|
453594ee9e | ||
|
|
a2d8bec80a | ||
|
|
b0ca8a8e6c | ||
|
|
99c9d777c0 | ||
|
|
97d109c900 | ||
|
|
35ad91a9e0 | ||
|
|
8bc16b020b | ||
|
|
6db9bd0ad9 | ||
|
|
5baa397d1c | ||
|
|
76e1243da3 | ||
|
|
7b7a0f9aa7 | ||
|
|
134e2236a6 | ||
|
|
aab672976e | ||
|
|
f7aee9fe18 | ||
|
|
a19b58675b | ||
|
|
7a229b27f5 | ||
|
|
abf68446e5 | ||
|
|
b4b7ec565e | ||
|
|
4b1b920bf4 | ||
|
|
11bff57f23 | ||
|
|
4838b0f6f0 | ||
|
|
bb8807129a | ||
|
|
cb050bfe34 | ||
|
|
76a996cc2d | ||
|
|
7fec1dd5cf | ||
|
|
a24e9089b6 | ||
|
|
b6474eaef6 | ||
|
|
76f90b0a46 | ||
|
|
42abffdaef |
@@ -10,8 +10,11 @@ else
|
|||||||
FNAME="amd64";
|
FNAME="amd64";
|
||||||
fi
|
fi
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-${ARCH}.zip"
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-${ARCH}.zip"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -o build/bin/geoip.dat
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -o build/bin/geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "build/bin/xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
mv main build/x-ui
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
|
cd ../../
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM golang:1.20-alpine AS builder
|
FROM golang:1.20-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk --no-cache --update add build-base gcc wget
|
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN env CGO_ENABLED=1 go build main.go
|
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
|
||||||
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -54,11 +54,17 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
|||||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||||
| `POST` | `"/update/:id"` | Update Inbound |
|
| `POST` | `"/update/:id"` | Update Inbound |
|
||||||
| `POST` | `"/addClient/"` | Add Client to inbound |
|
| `POST` | `"/addClient/"` | Add Client to inbound |
|
||||||
| `POST` | `"/delClient/:email"` | Delete Client |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* |
|
||||||
| `POST` | `"/updateClient/:index"` | Update Client |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
||||||
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
|
*- The field `clientId` should be filled by:
|
||||||
|
- `client.id` for VMESS and VLESS
|
||||||
|
- `client.password` for TROJAN
|
||||||
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.0.0
|
1.2.0
|
||||||
@@ -79,6 +79,7 @@ install_base() {
|
|||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
|
/usr/local/x-ui/x-ui migrate
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||||
|
|||||||
24
main.go
24
main.go
@@ -51,8 +51,8 @@ func runWebServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
//信号量捕获处理
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
@@ -133,7 +133,6 @@ func updateTgbotEnableSts(status bool) {
|
|||||||
logger.Infof("SetTgbotenabled[%v] success", status)
|
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||||
@@ -204,6 +203,19 @@ func updateSetting(port int, username string, password string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateDb() {
|
||||||
|
inboundService := service.InboundService{}
|
||||||
|
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Start migrating database...")
|
||||||
|
inboundService.MigrationRequirements()
|
||||||
|
inboundService.RemoveOrphanedTraffics()
|
||||||
|
fmt.Println("Migration done!")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
runWebServer()
|
runWebServer()
|
||||||
@@ -246,6 +258,7 @@ func main() {
|
|||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" v2-ui migrate form v2-ui")
|
fmt.Println(" v2-ui migrate form v2-ui")
|
||||||
|
fmt.Println(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +276,8 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWebServer()
|
runWebServer()
|
||||||
|
case "migrate":
|
||||||
|
migrateDb()
|
||||||
case "v2-ui":
|
case "v2-ui":
|
||||||
err := v2uiCmd.Parse(os.Args[2:])
|
err := v2uiCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,6 +305,9 @@ func main() {
|
|||||||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
}
|
}
|
||||||
|
if enabletgbot {
|
||||||
|
updateTgbotEnableSts(enabletgbot)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
.ant-card-dark .ant-modal-close,
|
.ant-card-dark .ant-modal-close,
|
||||||
.ant-card-dark i,
|
.ant-card-dark i,
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item,
|
.ant-card-dark .ant-select-dropdown-menu-item,
|
||||||
|
.ant-card-dark .ant-calendar-day-select,
|
||||||
.ant-card-dark .ant-calendar-month-select,
|
.ant-card-dark .ant-calendar-month-select,
|
||||||
.ant-card-dark .ant-calendar-year-select,
|
.ant-card-dark .ant-calendar-year-select,
|
||||||
.ant-card-dark .ant-calendar-date,
|
.ant-card-dark .ant-calendar-date,
|
||||||
@@ -226,7 +227,7 @@
|
|||||||
.ant-card-dark .ant-calendar-date:hover,
|
.ant-card-dark .ant-calendar-date:hover,
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||||
background-color: #004488;
|
background-color: #11314d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark tbody .ant-table-expanded-row,
|
.ant-card-dark tbody .ant-table-expanded-row,
|
||||||
@@ -243,7 +244,7 @@
|
|||||||
.ant-card-dark .ant-select-selection,
|
.ant-card-dark .ant-select-selection,
|
||||||
.ant-card-dark .ant-calendar-picker-clear {
|
.ant-card-dark .ant-calendar-picker-clear {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #2e3b52;
|
background-color: #193752;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
@@ -264,12 +265,19 @@
|
|||||||
|
|
||||||
.ant-card-dark .ant-modal-content,
|
.ant-card-dark .ant-modal-content,
|
||||||
.ant-card-dark .ant-modal-body,
|
.ant-card-dark .ant-modal-body,
|
||||||
.ant-card-dark .ant-modal-header,
|
.ant-card-dark .ant-modal-header {
|
||||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #222a37;
|
background-color: #222a37;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||||
|
background-color: #1668dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
||||||
|
background: #1668dc;
|
||||||
|
}
|
||||||
|
|
||||||
.client-table-header {
|
.client-table-header {
|
||||||
background-color: #f0f2f5;
|
background-color: #f0f2f5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ class AllSetting {
|
|||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
this.webBasePath = "/";
|
this.webBasePath = "/";
|
||||||
|
this.sessionMaxAge = "";
|
||||||
this.expireDiff = "";
|
this.expireDiff = "";
|
||||||
this.trafficDiff = "";
|
this.trafficDiff = "";
|
||||||
this.tgBotEnable = false;
|
this.tgBotEnable = false;
|
||||||
|
|||||||
@@ -21,26 +21,12 @@ const SSMethods = {
|
|||||||
// AES_128_CFB: 'aes-128-cfb',
|
// AES_128_CFB: 'aes-128-cfb',
|
||||||
// CHACHA20: 'chacha20',
|
// CHACHA20: 'chacha20',
|
||||||
// CHACHA20_IETF: 'chacha20-ietf',
|
// CHACHA20_IETF: 'chacha20-ietf',
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
// CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
// AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
// AES_128_GCM: 'aes-128-gcm',
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
// BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
};
|
|
||||||
|
|
||||||
const RULE_IP = {
|
|
||||||
PRIVATE: 'geoip:private',
|
|
||||||
CN: 'geoip:cn',
|
|
||||||
};
|
|
||||||
|
|
||||||
const RULE_DOMAIN = {
|
|
||||||
ADS: 'geosite:category-ads',
|
|
||||||
ADS_ALL: 'geosite:category-ads-all',
|
|
||||||
CN: 'geosite:cn',
|
|
||||||
GOOGLE: 'geosite:google',
|
|
||||||
FACEBOOK: 'geosite:facebook',
|
|
||||||
SPEEDTEST: 'geosite:speedtest',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TLS_FLOW_CONTROL = {
|
const TLS_FLOW_CONTROL = {
|
||||||
@@ -89,20 +75,25 @@ const UTLS_FINGERPRINT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
H3: "h3",
|
|
||||||
H2: "h2",
|
|
||||||
HTTP1: "http/1.1",
|
HTTP1: "http/1.1",
|
||||||
|
H2: "h2",
|
||||||
|
H3: "h3",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SNIFFING_OPTION = {
|
||||||
|
HTTP: "http",
|
||||||
|
TLS: "tls",
|
||||||
|
QUIC: "quic",
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(VmessMethods);
|
Object.freeze(VmessMethods);
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(RULE_IP);
|
|
||||||
Object.freeze(RULE_DOMAIN);
|
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_VERSION_OPTION);
|
Object.freeze(TLS_VERSION_OPTION);
|
||||||
Object.freeze(TLS_CIPHER_OPTION);
|
Object.freeze(TLS_CIPHER_OPTION);
|
||||||
Object.freeze(ALPN_OPTION);
|
Object.freeze(ALPN_OPTION);
|
||||||
|
Object.freeze(SNIFFING_OPTION);
|
||||||
|
|
||||||
class XrayCommonClass {
|
class XrayCommonClass {
|
||||||
|
|
||||||
@@ -451,18 +442,20 @@ class QuicStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(serviceName="") {
|
constructor(serviceName="", multiMode=false) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
|
this.multiMode = multiMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new GrpcStreamSettings(json.serviceName);
|
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
|
multiMode: this.multiMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,7 +640,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(publicKey = '', fingerprint = '', serverName = '', spiderX= '/') {
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
@@ -755,7 +748,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Sniffing extends XrayCommonClass {
|
class Sniffing extends XrayCommonClass {
|
||||||
constructor(enabled=true, destOverride=['http', 'tls']) {
|
constructor(enabled=true, destOverride=['http', 'tls', 'quic']) {
|
||||||
super();
|
super();
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.destOverride = destOverride;
|
this.destOverride = destOverride;
|
||||||
@@ -765,7 +758,7 @@ class Sniffing extends XrayCommonClass {
|
|||||||
let destOverride = ObjectUtil.clone(json.destOverride);
|
let destOverride = ObjectUtil.clone(json.destOverride);
|
||||||
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
||||||
if (ObjectUtil.isEmpty(destOverride[0])) {
|
if (ObjectUtil.isEmpty(destOverride[0])) {
|
||||||
destOverride = ['http', 'tls'];
|
destOverride = ['http', 'tls', 'quic'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Sniffing(
|
return new Sniffing(
|
||||||
@@ -867,66 +860,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.network === "http";
|
return this.network === "http";
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMess & VLess
|
|
||||||
get uuid() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].id;
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].id;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VLess & Trojan
|
|
||||||
get flow() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VLESS:
|
|
||||||
return this.settings.vlesses[0].flow;
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].flow;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VMess
|
|
||||||
get alterId() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.VMESS:
|
|
||||||
return this.settings.vmesses[0].alterId;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Socks & HTTP
|
|
||||||
get username() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].user;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trojan & Shadowsocks & Socks & HTTP
|
|
||||||
get password() {
|
|
||||||
switch (this.protocol) {
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
return this.settings.trojans[0].password;
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return this.settings.password;
|
|
||||||
case Protocols.SOCKS:
|
|
||||||
case Protocols.HTTP:
|
|
||||||
return this.settings.accounts[0].pass;
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shadowsocks
|
// Shadowsocks
|
||||||
get method() {
|
get method() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
@@ -1000,10 +933,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
if(this.settings.vlesses[index].expiryTime > 0)
|
if(this.settings.vlesses[index].expiryTime > 0)
|
||||||
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if(this.settings.trojans[index].expiryTime > 0)
|
if(this.settings.trojans[index].expiryTime > 0)
|
||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
if(this.settings.shadowsockses[index].expiryTime > 0)
|
||||||
|
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1014,7 +951,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1073,7 +1009,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1106,50 +1041,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.protocol !== Protocols.VMESS) {
|
if (this.protocol !== Protocols.VMESS) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
let network = this.stream.network;
|
|
||||||
let type = 'none';
|
|
||||||
let host = '';
|
|
||||||
let path = '';
|
|
||||||
if (network === 'tcp') {
|
|
||||||
let tcp = this.stream.tcp;
|
|
||||||
type = tcp.type;
|
|
||||||
if (type === 'http') {
|
|
||||||
let request = tcp.request;
|
|
||||||
path = request.path.join(',');
|
|
||||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
|
||||||
if (index >= 0) {
|
|
||||||
host = request.headers[index].value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (network === 'kcp') {
|
|
||||||
let kcp = this.stream.kcp;
|
|
||||||
type = kcp.type;
|
|
||||||
path = kcp.seed;
|
|
||||||
} else if (network === 'ws') {
|
|
||||||
let ws = this.stream.ws;
|
|
||||||
path = ws.path;
|
|
||||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
|
||||||
if (index >= 0) {
|
|
||||||
host = ws.headers[index].value;
|
|
||||||
}
|
|
||||||
} else if (network === 'http') {
|
|
||||||
network = 'h2';
|
|
||||||
path = this.stream.http.path;
|
|
||||||
host = this.stream.http.host.join(',');
|
|
||||||
} else if (network === 'quic') {
|
|
||||||
type = this.stream.quic.type;
|
|
||||||
host = this.stream.quic.security;
|
|
||||||
path = this.stream.quic.key;
|
|
||||||
} else if (network === 'grpc') {
|
|
||||||
path = this.stream.grpc.serviceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.stream.security === 'tls') {
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
|
||||||
address = this.stream.tls.server;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj = {
|
let obj = {
|
||||||
v: '2',
|
v: '2',
|
||||||
ps: remark,
|
ps: remark,
|
||||||
@@ -1157,16 +1048,66 @@ class Inbound extends XrayCommonClass {
|
|||||||
port: this.port,
|
port: this.port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: this.settings.vmesses[clientIndex].id,
|
||||||
aid: this.settings.vmesses[clientIndex].alterId,
|
aid: this.settings.vmesses[clientIndex].alterId,
|
||||||
net: network,
|
net: this.stream.network,
|
||||||
type: type,
|
type: 'none',
|
||||||
host: host,
|
|
||||||
path: path,
|
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
sni: this.stream.tls.settings.serverName,
|
|
||||||
fp: this.stream.tls.settings.fingerprint,
|
|
||||||
alpn: this.stream.tls.alpn.join(','),
|
|
||||||
allowInsecure: this.stream.tls.settings.allowInsecure,
|
|
||||||
};
|
};
|
||||||
|
let network = this.stream.network;
|
||||||
|
if (network === 'tcp') {
|
||||||
|
let tcp = this.stream.tcp;
|
||||||
|
obj.type = tcp.type;
|
||||||
|
if (tcp.type === 'http') {
|
||||||
|
let request = tcp.request;
|
||||||
|
obj.path = request.path.join(',');
|
||||||
|
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
obj.host = request.headers[index].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (network === 'kcp') {
|
||||||
|
let kcp = this.stream.kcp;
|
||||||
|
obj.type = kcp.type;
|
||||||
|
obj.path = kcp.seed;
|
||||||
|
} else if (network === 'ws') {
|
||||||
|
let ws = this.stream.ws;
|
||||||
|
obj.path = ws.path;
|
||||||
|
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
obj.host = ws.headers[index].value;
|
||||||
|
}
|
||||||
|
} else if (network === 'http') {
|
||||||
|
obj.net = 'h2';
|
||||||
|
obj.path = this.stream.http.path;
|
||||||
|
obj.host = this.stream.http.host.join(',');
|
||||||
|
} else if (network === 'quic') {
|
||||||
|
obj.type = this.stream.quic.type;
|
||||||
|
obj.host = this.stream.quic.security;
|
||||||
|
obj.path = this.stream.quic.key;
|
||||||
|
} else if (network === 'grpc') {
|
||||||
|
obj.path = this.stream.grpc.serviceName;
|
||||||
|
if (this.stream.grpc.multiMode){
|
||||||
|
obj.type = 'multi'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stream.security === 'tls') {
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
|
obj.add = this.stream.tls.server;
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
|
||||||
|
obj.sni = this.stream.tls.settings.serverName;
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
||||||
|
obj.fp = this.stream.tls.settings.fingerprint;
|
||||||
|
}
|
||||||
|
if (this.stream.tls.alpn.length>0){
|
||||||
|
obj.alpn = this.stream.tls.alpn.join(',');
|
||||||
|
}
|
||||||
|
if (this.stream.tls.settings.allowInsecure){
|
||||||
|
obj.allowInsecure = this.stream.tls.settings.allowInsecure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,6 +1160,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
if(grpc.multiMode){
|
||||||
|
params.set("mode", "multi");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1243,15 +1187,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.fingerprint)) {
|
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
@@ -1272,18 +1214,19 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', remark='') {
|
genSSLink(address='', remark='', clientIndex = 0) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const server = this.stream.tls.server;
|
const port = this.port;
|
||||||
if (!ObjectUtil.isEmpty(server)) {
|
|
||||||
address = server;
|
return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark);
|
||||||
}
|
|
||||||
if (settings.method == SSMethods.BLAKE3_AES_128_GCM || settings.method == SSMethods.BLAKE3_AES_256_GCM || settings.method == SSMethods.BLAKE3_CHACHA20_POLY1305) {
|
|
||||||
return `ss://${settings.method}:${settings.password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
// if (settings.method == SSMethods.BLAKE3_AES_128_GCM || settings.method == SSMethods.BLAKE3_AES_256_GCM || settings.method == SSMethods.BLAKE3_CHACHA20_POLY1305) {
|
||||||
} else {
|
// return `ss://${settings.method}:${settings.password}@${address}:${this.port}#${encodeURIComponent(remark)}`;
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password + '@' + address + ':' + this.port)
|
// } else {
|
||||||
+ '#' + encodeURIComponent(remark);
|
// return 'ss://' + safeBase64(settings.method + ':' + settings.password + '@' + address + ':' + this.port)
|
||||||
}
|
// + '#' + encodeURIComponent(remark);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1334,6 +1277,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
if(grpc.multiMode){
|
||||||
|
params.set("mode", "multi");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1355,15 +1301,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.fingerprint)) {
|
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
@@ -1396,7 +1340,11 @@ class Inbound extends XrayCommonClass {
|
|||||||
remark = this.settings.vlesses[clientIndex].email
|
remark = this.settings.vlesses[clientIndex].email
|
||||||
}
|
}
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
|
case Protocols.SHADOWSOCKS:
|
||||||
|
if (this.settings.shadowsockses[clientIndex].email != ""){
|
||||||
|
remark = this.settings.shadowsockses[clientIndex].email
|
||||||
|
}
|
||||||
|
return this.genSSLink(address, remark, clientIndex);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
if (this.settings.trojans[clientIndex].email != ""){
|
||||||
remark = this.settings.trojans[clientIndex].email
|
remark = this.settings.trojans[clientIndex].email
|
||||||
@@ -1853,13 +1801,15 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
|||||||
Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
method=SSMethods.BLAKE3_AES_256_GCM,
|
method=SSMethods.BLAKE3_AES_256_GCM,
|
||||||
password=RandomUtil.randomSeq(44),
|
password=RandomUtil.randomShadowsocksPassword(),
|
||||||
network='tcp,udp'
|
network='tcp,udp',
|
||||||
|
shadowsockses=[new Inbound.ShadowsocksSettings.Shadowsocks()]
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
|
this.shadowsockses = shadowsockses;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -1868,6 +1818,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
json.method,
|
json.method,
|
||||||
json.password,
|
json.password,
|
||||||
json.network,
|
json.network,
|
||||||
|
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1876,10 +1827,74 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
method: this.method,
|
method: this.method,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
network: this.network,
|
network: this.network,
|
||||||
|
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
|
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
||||||
|
super();
|
||||||
|
this.password = password;
|
||||||
|
this.email = email;
|
||||||
|
this.totalGB = totalGB;
|
||||||
|
this.expiryTime = expiryTime;
|
||||||
|
this.enable = enable;
|
||||||
|
this.tgId = tgId;
|
||||||
|
this.subId = subId;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
password: this.password,
|
||||||
|
email: this.email,
|
||||||
|
totalGB: this.totalGB,
|
||||||
|
expiryTime: this.expiryTime,
|
||||||
|
enable: this.enable,
|
||||||
|
tgId: this.tgId,
|
||||||
|
subId: this.subId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||||
|
json.password,
|
||||||
|
json.email,
|
||||||
|
json.totalGB,
|
||||||
|
json.expiryTime,
|
||||||
|
json.enable,
|
||||||
|
json.tgId,
|
||||||
|
json.subId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get _expiryTime() {
|
||||||
|
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.expiryTime < 0){
|
||||||
|
return this.expiryTime / -86400000;
|
||||||
|
}
|
||||||
|
return moment(this.expiryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _expiryTime(t) {
|
||||||
|
if (t == null || t === "") {
|
||||||
|
this.expiryTime = 0;
|
||||||
|
} else {
|
||||||
|
this.expiryTime = t.valueOf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get _totalGB() {
|
||||||
|
return toFixed(this.totalGB / ONE_GB, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
set _totalGB(gb) {
|
||||||
|
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -146,6 +146,12 @@ class RandomUtil {
|
|||||||
}
|
}
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randomShadowsocksPassword(){
|
||||||
|
let array = new Uint8Array(32);
|
||||||
|
window.crypto.getRandomValues(array);
|
||||||
|
return btoa(String.fromCharCode.apply(null, array));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
g.GET("/", a.inbounds)
|
g.GET("/", a.inbounds)
|
||||||
g.GET("/get/:id", a.inbound)
|
g.GET("/get/:id", a.inbound)
|
||||||
|
g.GET("/getClientTraffics/:email", a.getClientTraffics)
|
||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/addClient/", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,9 @@ func (a *APIController) inbounds(c *gin.Context) {
|
|||||||
func (a *APIController) inbound(c *gin.Context) {
|
func (a *APIController) inbound(c *gin.Context) {
|
||||||
a.inboundController.getInbound(c)
|
a.inboundController.getInbound(c)
|
||||||
}
|
}
|
||||||
|
func (a *APIController) getClientTraffics(c *gin.Context) {
|
||||||
|
a.inboundController.getClientTraffics(c)
|
||||||
|
}
|
||||||
func (a *APIController) addInbound(c *gin.Context) {
|
func (a *APIController) addInbound(c *gin.Context) {
|
||||||
a.inboundController.addInbound(c)
|
a.inboundController.addInbound(c)
|
||||||
}
|
}
|
||||||
@@ -65,3 +70,6 @@ func (a *APIController) resetAllTraffics(c *gin.Context) {
|
|||||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||||
a.inboundController.resetAllClientTraffics(c)
|
a.inboundController.resetAllClientTraffics(c)
|
||||||
}
|
}
|
||||||
|
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
|
a.inboundController.delDepletedClients(c)
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/addClient", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +76,15 @@ func (a *InboundController) getInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
}
|
}
|
||||||
|
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
||||||
|
email := c.Param("email")
|
||||||
|
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error getting traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, clientTraffics, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInbound(c *gin.Context) {
|
func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
@@ -138,7 +148,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.inboundService.AddInboundClient(data)
|
err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
@@ -148,17 +158,16 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
email := c.Param("email")
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
inbound := &model.Inbound{}
|
|
||||||
err := c.ShouldBind(inbound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
err = a.inboundService.DelInboundClient(inbound, email)
|
err = a.inboundService.DelInboundClient(id, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
@@ -168,22 +177,18 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||||
index, err := strconv.Atoi(c.Param("index"))
|
clientId := c.Param("clientId")
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err = c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateInboundClient(inbound, index)
|
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
@@ -202,7 +207,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.inboundService.ResetClientTraffic(id, email)
|
err = a.inboundService.ResetClientTraffic(id, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic reseted", nil)
|
||||||
@@ -214,7 +219,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||||
err := a.inboundService.ResetAllTraffics()
|
err := a.inboundService.ResetAllTraffics()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
@@ -229,8 +234,22 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.inboundService.ResetAllClientTraffics(id)
|
err = a.inboundService.ResetAllClientTraffics(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = a.inboundService.DelDepletedClients(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ type LoginForm struct {
|
|||||||
type IndexController struct {
|
type IndexController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
userService service.UserService
|
settingService service.SettingService
|
||||||
tgbot service.Tgbot
|
userService service.UserService
|
||||||
|
tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
func NewIndexController(g *gin.RouterGroup) *IndexController {
|
||||||
@@ -69,6 +70,16 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("Unable to get session's max age from DB")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("Unable to set session's max age")
|
||||||
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Info("user", user.Id, "login success")
|
logger.Info("user", user.Id, "login success")
|
||||||
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/update", a.updateSetting)
|
g.POST("/update", a.updateSetting)
|
||||||
g.POST("/updateUser", a.updateUser)
|
g.POST("/updateUser", a.updateUser)
|
||||||
g.POST("/restartPanel", a.restartPanel)
|
g.POST("/restartPanel", a.restartPanel)
|
||||||
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
@@ -118,3 +119,12 @@ func (a *SettingController) restartPanel(c *gin.Context) {
|
|||||||
err := a.panelService.RestartPanel(time.Second * 3)
|
err := a.panelService.RestartPanel(time.Second * 3)
|
||||||
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add subscription-userinfo
|
// Add subscription-userinfo
|
||||||
c.Writer.Header().Set("subscription-userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"x-ui/web/entity"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getUriId(c *gin.Context) int64 {
|
|
||||||
s := struct {
|
|
||||||
Id int64 `uri:"id"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
_ = c.BindUri(&s)
|
|
||||||
return s.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRemoteIp(c *gin.Context) string {
|
func getRemoteIp(c *gin.Context) string {
|
||||||
value := c.GetHeader("X-Forwarded-For")
|
value := c.GetHeader("X-Forwarded-For")
|
||||||
if value != "" {
|
if value != "" {
|
||||||
@@ -75,6 +67,7 @@ func html(c *gin.Context, name string, title string, data gin.H) {
|
|||||||
data = gin.H{}
|
data = gin.H{}
|
||||||
}
|
}
|
||||||
data["title"] = title
|
data["title"] = title
|
||||||
|
data["host"] = strings.Split(c.Request.Host, ":")[0]
|
||||||
data["request_uri"] = c.Request.RequestURI
|
data["request_uri"] = c.Request.RequestURI
|
||||||
data["base_path"] = c.GetString("base_path")
|
data["base_path"] = c.GetString("base_path")
|
||||||
c.HTML(http.StatusOK, name, getContext(data))
|
c.HTML(http.StatusOK, name, getContext(data))
|
||||||
@@ -84,10 +77,8 @@ func getContext(h gin.H) gin.H {
|
|||||||
a := gin.H{
|
a := gin.H{
|
||||||
"cur_ver": config.GetVersion(),
|
"cur_ver": config.GetVersion(),
|
||||||
}
|
}
|
||||||
if h != nil {
|
for key, value := range h {
|
||||||
for key, value := range h {
|
a[key] = value
|
||||||
a[key] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ type AllSetting struct {
|
|||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||||
|
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
|
|||||||
@@ -7,12 +7,13 @@
|
|||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
|
||||||
<style>
|
<style>
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -17,12 +17,13 @@
|
|||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
clients: [],
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
|
oldClientId: "",
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
if(clientModal.isEdit){
|
if(clientModal.isEdit){
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||||
} else {
|
} else {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
}
|
}
|
||||||
@@ -38,12 +39,13 @@
|
|||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
if (!isEdit){
|
if (isEdit){
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
|
||||||
} else {
|
|
||||||
if (this.clients[index].expiryTime < 0){
|
if (this.clients[index].expiryTime < 0){
|
||||||
this.delayedStart = true;
|
this.delayedStart = true;
|
||||||
}
|
}
|
||||||
|
this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]);
|
||||||
|
} else {
|
||||||
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
}
|
}
|
||||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
@@ -53,14 +55,23 @@
|
|||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientId(protocol, client) {
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.TROJAN: return client.password;
|
||||||
|
case Protocols.SHADOWSOCKS: return client.email;
|
||||||
|
default: return client.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
addClient(protocol, clients) {
|
addClient(protocol, clients) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
|
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -122,6 +133,24 @@
|
|||||||
}
|
}
|
||||||
client.email = string;
|
client.email = string;
|
||||||
},
|
},
|
||||||
|
resetClientTraffic(email,dbInboundId,iconElement) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: async () => {
|
||||||
|
iconElement.disabled = true;
|
||||||
|
const msg = await HttpUtil.postWithModal('/xui/inbound/' + dbInboundId + '/resetClientTraffic/'+ email);
|
||||||
|
if (msg.success) {
|
||||||
|
this.clientModal.clientStats.up = 0;
|
||||||
|
this.clientModal.clientStats.down = 0;
|
||||||
|
}
|
||||||
|
iconElement.disabled = false;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input-number :value="value" @input="$emit('input', $event.target.value)"></a-input-number>
|
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)" :min="min"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'textarea'">
|
<template v-else-if="type === 'textarea'">
|
||||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
{{define "component/setting"}}
|
{{define "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('setting-list-item', {
|
Vue.component('setting-list-item', {
|
||||||
props: ["type", "title", "desc", "value"],
|
props: ["type", "title", "desc", "value", "min"],
|
||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,8 +28,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="inbound.protocol === Protocols.TROJAN">
|
<tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
<td>password</td>
|
<td>password
|
||||||
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
|
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
|
||||||
@@ -103,6 +105,10 @@
|
|||||||
[[ sizeFormat(clientStats.down) ]]
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||||
</a-tag>
|
</a-tag>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
|
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -133,7 +139,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -1,21 +1,133 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
|
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
||||||
|
<table width="100%" class="ant-table-tbody">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span>{{ i18n "pages.inbounds.Email" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>password
|
||||||
|
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input v-model.trim="client.password" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Subscription</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item v-if="client.email">
|
||||||
|
<a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Telegram Username</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item v-if="client.email">
|
||||||
|
<a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="delayedStart">
|
||||||
|
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-else>
|
||||||
|
<td>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
|
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
<a-collapse v-else>
|
||||||
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
||||||
|
<table width="100%">
|
||||||
|
<tr class="client-table-header">
|
||||||
|
<th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
|
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
<table width="100%" class="ant-table-tbody">
|
<table width="100%" class="ant-table-tbody">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}
|
||||||
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -12,5 +12,10 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||||
|
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -9,6 +9,14 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>MultiMode</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<table width="100%" class="ant-table-tbody">
|
<a-form-item label="{{ i18n "transmission" }}">
|
||||||
<tr>
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
<td>{{ i18n "transmission" }}</td>
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<td>
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
<a-form-item>
|
<a-select-option value="kcp">kcp</a-select-option>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select-option value="ws">ws</a-select-option>
|
||||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select-option value="http">http</a-select-option>
|
||||||
<a-select-option value="tcp">tcp</a-select-option>
|
<a-select-option value="quic">quic</a-select-option>
|
||||||
<a-select-option value="kcp">kcp</a-select-option>
|
<a-select-option value="grpc">grpc</a-select-option>
|
||||||
<a-select-option value="ws">ws</a-select-option>
|
</a-select>
|
||||||
<a-select-option value="http">http</a-select-option>
|
</a-form-item>
|
||||||
<a-select-option value="quic">quic</a-select-option>
|
|
||||||
<a-select-option value="grpc">grpc</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp -->
|
<!-- tcp -->
|
||||||
|
|||||||
@@ -56,7 +56,8 @@
|
|||||||
<td>uTLS</td>
|
<td>uTLS</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
|
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
<a-checkbox-group v-model="inbound.stream.tls.alpn">
|
||||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
</td>
|
||||||
@@ -175,8 +176,8 @@
|
|||||||
<td>uTLS</td>
|
<td>uTLS</td>
|
||||||
<td>
|
<td>
|
||||||
<a-form-item >
|
<a-form-item >
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
<a-select-option value=''>None</a-select-option>
|
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
<template v-if="inbound.isGrpc">
|
<template v-if="inbound.isGrpc">
|
||||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||||
|
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.reality">
|
<td v-else-if="inbound.reality">
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,9 +66,42 @@
|
|||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-row>
|
||||||
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||||
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||||
|
<a-menu-item key="export">
|
||||||
|
<a-icon type="export"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.export" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetInbounds">
|
||||||
|
<a-icon type="reload"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="resetClients">
|
||||||
|
<a-icon type="file-done"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delDepletedClients">
|
||||||
|
<a-icon type="rest"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
||||||
|
<a-select v-model="refreshInterval"
|
||||||
|
v-if="isRefreshEnabled"
|
||||||
|
@change="changeRefreshInterval"
|
||||||
|
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-icon type="sync" :spin="isRefreshEnabled" @click="manualRefresh"></a-icon>
|
||||||
|
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
@@ -81,15 +114,11 @@
|
|||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
|
<a-icon @click="e => e.preventDefault()" type="menu"></a-icon>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
||||||
<a-menu-item v-if="dbInbound.isSS" key="qrcode">
|
|
||||||
<a-icon type="qrcode"></a-icon>
|
|
||||||
{{ i18n "qrCode" }}
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="edit">
|
<a-menu-item key="edit">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -100,12 +129,16 @@
|
|||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="resetClients">
|
<a-menu-item key="resetClients">
|
||||||
<a-icon type="file-done"></a-icon>
|
<a-icon type="file-done"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics"}}
|
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delDepletedClients">
|
||||||
|
<a-icon type="rest"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
|
</a-menu-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-menu-item key="showInfo">
|
<a-menu-item key="showInfo">
|
||||||
@@ -129,7 +162,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">tls</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">reality</a-tag>
|
||||||
@@ -191,7 +224,7 @@
|
|||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-table
|
<a-table
|
||||||
v-else-if="record.protocol === Protocols.TROJAN"
|
v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS"
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerTrojanColumns"
|
:columns="innerTrojanColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
@@ -289,25 +322,22 @@
|
|||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: {},
|
clientCount: {},
|
||||||
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning=true) {
|
loading(spinning=true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
this.loading();
|
|
||||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
const msg = await HttpUtil.post('/xui/inbound/list');
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
this.searchKey = '';
|
|
||||||
},
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
this.loading();
|
|
||||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -319,17 +349,16 @@
|
|||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
this.searchedInbounds.splice(0);
|
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
const dbInbound = new DBInbound(inbound);
|
const dbInbound = new DBInbound(inbound);
|
||||||
to_inbound = dbInbound.toInbound()
|
to_inbound = dbInbound.toInbound()
|
||||||
this.inbounds.push(to_inbound);
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
this.searchedInbounds.push(dbInbound);
|
|
||||||
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
||||||
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.searchInbounds(this.searchKey);
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound,inbound){
|
getClientCounts(dbInbound,inbound){
|
||||||
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
||||||
@@ -387,6 +416,22 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
generalActions(action){
|
||||||
|
switch (action.key) {
|
||||||
|
case "export":
|
||||||
|
this.exportAllLinks();
|
||||||
|
break;
|
||||||
|
case "resetInbounds":
|
||||||
|
this.resetAllTraffic();
|
||||||
|
break;
|
||||||
|
case "resetClients":
|
||||||
|
this.resetAllClientTraffics(-1);
|
||||||
|
break;
|
||||||
|
case "delDepletedClients":
|
||||||
|
this.delDepletedClients(-1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
@@ -419,6 +464,9 @@
|
|||||||
case "delete":
|
case "delete":
|
||||||
this.delInbound(dbInbound.id);
|
this.delInbound(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "delDepletedClients":
|
||||||
|
this.delDepletedClients(dbInbound.id)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openAddInbound() {
|
openAddInbound() {
|
||||||
@@ -558,9 +606,9 @@
|
|||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
index: index,
|
index: index,
|
||||||
confirm: async (client, dbInboundId, index) => {
|
confirm: async (client, dbInboundId, clientId) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.updateClient(client, dbInboundId, index);
|
await this.updateClient(client, dbInboundId, clientId);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
@@ -577,12 +625,12 @@
|
|||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/addClient`, data);
|
await this.submit(`/xui/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, index) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() +']}',
|
settings: '{"clients": [' + client.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -612,22 +660,14 @@
|
|||||||
},
|
},
|
||||||
delClient(dbInboundId,client) {
|
delClient(dbInboundId,client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = new DBInbound(dbInbound);
|
clientId = clientId = getClientId(dbInbound.protocol,client);;
|
||||||
inbound = newDbInbound.toInbound();
|
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
|
||||||
index = this.findIndexOfClient(clients, client);
|
|
||||||
clients.splice(index, 1);
|
|
||||||
const data = {
|
|
||||||
id: dbInboundId,
|
|
||||||
settings: inbound.settings.toString(),
|
|
||||||
};
|
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/delClient/' + client.email, data),
|
onOk: () => this.submit(`/xui/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
@@ -635,9 +675,17 @@
|
|||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getClientId(protocol, client) {
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.TROJAN: return client.password;
|
||||||
|
case Protocols.SHADOWSOCKS: return client.email;
|
||||||
|
default: return client.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const link = dbInbound.genLink(clientIndex);
|
const link = dbInbound.genLink(clientIndex);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
|
||||||
@@ -656,7 +704,8 @@
|
|||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
await this.updateClient(clients[index],dbInboundId, index);
|
clientId = getClientId(dbInbound.protocol,clients[index]);
|
||||||
|
await this.updateClient(clients[index],dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
async submit(url, data) {
|
async submit(url, data) {
|
||||||
@@ -667,11 +716,13 @@
|
|||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
getInboundClients(dbInbound) {
|
||||||
if(dbInbound.protocol == Protocols.VLESS) {
|
if(dbInbound.protocol == Protocols.VLESS) {
|
||||||
return dbInbound.toInbound().settings.vlesses
|
return dbInbound.toInbound().settings.vlesses;
|
||||||
} else if(dbInbound.protocol == Protocols.VMESS) {
|
} else if(dbInbound.protocol == Protocols.VMESS) {
|
||||||
return dbInbound.toInbound().settings.vmesses
|
return dbInbound.toInbound().settings.vmesses;
|
||||||
} else if(dbInbound.protocol == Protocols.TROJAN) {
|
} else if(dbInbound.protocol == Protocols.TROJAN) {
|
||||||
return dbInbound.toInbound().settings.trojans
|
return dbInbound.toInbound().settings.trojans;
|
||||||
|
} else if(dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
||||||
|
return dbInbound.toInbound().settings.shadowsockses;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetClientTraffic(client,dbInboundId) {
|
resetClientTraffic(client,dbInboundId) {
|
||||||
@@ -696,14 +747,24 @@
|
|||||||
},
|
},
|
||||||
resetAllClientTraffics(dbInboundId) {
|
resetAllClientTraffics(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
delDepletedClients(dbInboundId) {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
|
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||||
|
okText: '{{ i18n "reset"}}',
|
||||||
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
|
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||||
|
})
|
||||||
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index)
|
||||||
},
|
},
|
||||||
@@ -740,6 +801,32 @@
|
|||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
||||||
},
|
},
|
||||||
|
async startDataRefreshLoop() {
|
||||||
|
while (this.isRefreshEnabled) {
|
||||||
|
try {
|
||||||
|
await this.getDBInbounds();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await PromiseUtil.sleep(this.refreshInterval);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleRefresh() {
|
||||||
|
localStorage.setItem("isRefreshEnabled", this.isRefreshEnabled);
|
||||||
|
if (this.isRefreshEnabled) {
|
||||||
|
this.startDataRefreshLoop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeRefreshInterval(){
|
||||||
|
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||||
|
},
|
||||||
|
async manualRefresh(){
|
||||||
|
if(!this.isRefreshEnabled){
|
||||||
|
this.spinning = true;
|
||||||
|
await this.getDBInbounds();
|
||||||
|
this.spinning = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey: debounce(function (newVal) {
|
searchKey: debounce(function (newVal) {
|
||||||
@@ -747,8 +834,15 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
this.getDBInbounds();
|
if (this.isRefreshEnabled) {
|
||||||
|
this.startDataRefreshLoop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.getDBInbounds();
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
total() {
|
total() {
|
||||||
@@ -775,7 +869,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||||
|
<setting-list-item type="number" title='{{ i18n "pages.setting.sessionMaxAge" }}' desc='{{ i18n "pages.setting.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
@@ -112,6 +113,9 @@
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
||||||
|
<a-space direction="horizontal" style="padding: 0 20px">
|
||||||
|
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
|
||||||
|
</a-space>
|
||||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@@ -200,7 +204,16 @@
|
|||||||
await PromiseUtil.sleep(5000);
|
await PromiseUtil.sleep(5000);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
async resetXrayConfigToDefault() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getAllSetting();
|
await this.getAllSetting();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
@@ -300,26 +301,57 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
|||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string) error {
|
func (s *InboundService) DelInboundClient(inboundId int, clientId string) error {
|
||||||
db := database.GetDB()
|
oldInbound, err := s.GetInbound(inboundId)
|
||||||
err := s.DelClientStat(db, email)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Delete stats Data Error")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Load Old Data Error")
|
logger.Error("Load Old Data Error")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
email := ""
|
||||||
|
client_key := "id"
|
||||||
|
if oldInbound.Protocol == "trojan" {
|
||||||
|
client_key = "password"
|
||||||
|
}
|
||||||
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
client_key = "email"
|
||||||
|
}
|
||||||
|
|
||||||
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range inerfaceClients {
|
||||||
|
c := client.(map[string]interface{})
|
||||||
|
c_id := c[client_key].(string)
|
||||||
|
if c_id == clientId {
|
||||||
|
email = c["email"].(string)
|
||||||
|
} else {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = newClients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
|
db := database.GetDB()
|
||||||
|
err = s.DelClientStat(db, email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Delete stats Data Error")
|
||||||
|
return err
|
||||||
|
}
|
||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
||||||
clients, err := s.getClients(data)
|
clients, err := s.getClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -343,7 +375,25 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
|
oldEmail := ""
|
||||||
|
clientIndex := 0
|
||||||
|
for index, oldClient := range oldClients {
|
||||||
|
oldClientId := ""
|
||||||
|
if oldInbound.Protocol == "trojan" {
|
||||||
|
oldClientId = oldClient.Password
|
||||||
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
oldClientId = oldClient.Email
|
||||||
|
} else {
|
||||||
|
oldClientId = oldClient.ID
|
||||||
|
}
|
||||||
|
if clientId == oldClientId {
|
||||||
|
oldEmail = oldClient.Email
|
||||||
|
clientIndex = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -358,10 +408,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsClients := oldSettings["clients"].([]interface{})
|
settingsClients := oldSettings["clients"].([]interface{})
|
||||||
settingsClients[index] = inerfaceClients[0]
|
settingsClients[clientIndex] = inerfaceClients[0]
|
||||||
|
|
||||||
oldSettings["clients"] = settingsClients
|
oldSettings["clients"] = settingsClients
|
||||||
|
|
||||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
@@ -373,8 +421,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
|||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
if len(clients[0].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldClients[index].Email) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
|
err = s.UpdateClientStat(oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -382,7 +430,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
|||||||
s.AddClientStat(data.Id, &clients[0])
|
s.AddClientStat(data.Id, &clients[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.DelClientStat(db, oldClients[index].Email)
|
err = s.DelClientStat(db, oldEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -390,45 +438,35 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
|||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) {
|
func (s *InboundService) AddTraffic(traffics []*xray.Traffic) error {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
// Update traffics in a single transaction
|
||||||
db = db.Model(model.Inbound{})
|
err := database.GetDB().Transaction(func(tx *gorm.DB) error {
|
||||||
tx := db.Begin()
|
for _, traffic := range traffics {
|
||||||
defer func() {
|
if traffic.IsInbound {
|
||||||
if err != nil {
|
update := tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||||
tx.Rollback()
|
Updates(map[string]interface{}{
|
||||||
} else {
|
"up": gorm.Expr("up + ?", traffic.Up),
|
||||||
tx.Commit()
|
"down": gorm.Expr("down + ?", traffic.Down),
|
||||||
}
|
})
|
||||||
}()
|
if update.Error != nil {
|
||||||
for _, traffic := range traffics {
|
return update.Error
|
||||||
if traffic.IsInbound {
|
}
|
||||||
err = tx.Where("tag = ?", traffic.Tag).
|
|
||||||
UpdateColumns(map[string]interface{}{
|
|
||||||
"up": gorm.Expr("up + ?", traffic.Up),
|
|
||||||
"down": gorm.Expr("down + ?", traffic.Down)}).Error
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
traffics, err = s.adjustTraffics(traffics)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db = db.Model(xray.ClientTraffic{})
|
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -439,7 +477,32 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = tx.Save(traffics).Error
|
emails := make([]string, 0, len(traffics))
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
emails = append(emails, traffic.Email)
|
||||||
|
}
|
||||||
|
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for dbTraffic_index := range dbClientTraffics {
|
||||||
|
for traffic_index := range traffics {
|
||||||
|
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
||||||
|
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
||||||
|
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(dbClientTraffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
}
|
}
|
||||||
@@ -447,81 +510,56 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) adjustTraffics(traffics []*xray.ClientTraffic) (full_traffics []*xray.ClientTraffic, err error) {
|
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
inboundIds := make([]int, 0, len(dbClientTraffics))
|
||||||
dbInbound := db.Model(model.Inbound{})
|
for _, dbClientTraffic := range dbClientTraffics {
|
||||||
txInbound := dbInbound.Begin()
|
if dbClientTraffic.ExpiryTime < 0 {
|
||||||
|
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
txInbound.Rollback()
|
|
||||||
} else {
|
|
||||||
txInbound.Commit()
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
inbound := &model.Inbound{}
|
|
||||||
client_traffic := &xray.ClientTraffic{}
|
|
||||||
err := db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(client_traffic).Error
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
logger.Warning(err, traffic.Email)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
client_traffic.Up += traffic.Up
|
|
||||||
client_traffic.Down += traffic.Down
|
|
||||||
|
|
||||||
err = txInbound.Where("id=?", client_traffic.InboundId).First(inbound).Error
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
logger.Warning(err, traffic.Email)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// get clients
|
|
||||||
clients, err := s.getClients(inbound)
|
|
||||||
needUpdate := false
|
|
||||||
if err == nil {
|
|
||||||
for client_index, client := range clients {
|
|
||||||
if traffic.Email == client.Email {
|
|
||||||
if client.ExpiryTime < 0 {
|
|
||||||
clients[client_index].ExpiryTime = (time.Now().Unix() * 1000) - client.ExpiryTime
|
|
||||||
needUpdate = true
|
|
||||||
}
|
|
||||||
client_traffic.ExpiryTime = client.ExpiryTime
|
|
||||||
client_traffic.Total = client.TotalGB
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needUpdate {
|
|
||||||
settings := map[string]interface{}{}
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
|
||||||
|
|
||||||
// Convert clients to []interface to update clients in settings
|
|
||||||
var clientsInterface []interface{}
|
|
||||||
for _, c := range clients {
|
|
||||||
clientsInterface = append(clientsInterface, interface{}(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
settings["clients"] = clientsInterface
|
|
||||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = txInbound.Where("id=?", inbound.Id).Update("settings", string(modifiedSettings)).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
full_traffics = append(full_traffics, client_traffic)
|
|
||||||
}
|
}
|
||||||
return full_traffics, nil
|
|
||||||
|
if len(inboundIds) > 0 {
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients, ok := settings["clients"].([]interface{})
|
||||||
|
if ok {
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for traffic_index := range dbClientTraffics {
|
||||||
|
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||||
|
oldExpiryTime := c["expiryTime"].(float64)
|
||||||
|
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
||||||
|
c["expiryTime"] = newExpiryTime
|
||||||
|
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Save(inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("AddClientTraffic update inbounds ", err)
|
||||||
|
logger.Error(inbounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
@@ -611,8 +649,15 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
|||||||
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
whereText := "inbound_id "
|
||||||
|
if id == -1 {
|
||||||
|
whereText += " > ?"
|
||||||
|
} else {
|
||||||
|
whereText += " = ?"
|
||||||
|
}
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("inbound_id = ?", id).
|
Where(whereText, id).
|
||||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
||||||
err := result.Error
|
err := result.Error
|
||||||
@@ -638,6 +683,84 @@ func (s *InboundService) ResetAllTraffics() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
whereText := "inbound_id "
|
||||||
|
if id < 0 {
|
||||||
|
whereText += "> ?"
|
||||||
|
} else {
|
||||||
|
whereText += "= ?"
|
||||||
|
}
|
||||||
|
|
||||||
|
depletedClients := []xray.ClientTraffic{}
|
||||||
|
err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, depletedClient := range depletedClients {
|
||||||
|
emails := strings.Split(depletedClient.Email, ",")
|
||||||
|
oldInbound, err := s.GetInbound(depletedClient.InboundId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var oldSettings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldClients := oldSettings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range oldClients {
|
||||||
|
deplete := false
|
||||||
|
c := client.(map[string]interface{})
|
||||||
|
for _, email := range emails {
|
||||||
|
if email == c["email"].(string) {
|
||||||
|
deplete = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !deplete {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newClients) > 0 {
|
||||||
|
oldSettings["clients"] = newClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
|
err = tx.Save(oldInbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete inbound if no client remains
|
||||||
|
s.DelInbound(depletedClient.InboundId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
@@ -668,18 +791,20 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
|
|||||||
return traffics, err
|
return traffics, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.ClientTraffic, err error) {
|
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var traffics []*xray.ClientTraffic
|
var traffics []*xray.ClientTraffic
|
||||||
|
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email like ?", "%"+email+"%").Find(&traffics).Error
|
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
logger.Warning(err)
|
||||||
logger.Warning(err)
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return traffics, err
|
if len(traffics) > 0 {
|
||||||
|
return traffics[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||||
@@ -730,3 +855,65 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
|||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) MigrationRequirements() {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
// Fix inbounds based problems
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients, ok := settings["clients"].([]interface{})
|
||||||
|
if ok {
|
||||||
|
// Fix Clinet configuration problems
|
||||||
|
var newClients []interface{}
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
|
||||||
|
// Add email='' if it is not exists
|
||||||
|
if _, ok := c["email"]; !ok {
|
||||||
|
c["email"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "flow": "xtls-rprx-direct"
|
||||||
|
if _, ok := c["flow"]; ok {
|
||||||
|
if c["flow"] == "xtls-rprx-direct" {
|
||||||
|
c["flow"] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClients = append(newClients, interface{}(c))
|
||||||
|
}
|
||||||
|
settings["clients"] = newClients
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add client traffic row for all clients which has email
|
||||||
|
modelClients, err := s.getClients(inbounds[inbound_index])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, modelClient := range modelClients {
|
||||||
|
if len(modelClient.Email) > 0 {
|
||||||
|
var count int64
|
||||||
|
db.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||||
|
if count == 0 {
|
||||||
|
s.AddClientStat(inbounds[inbound_index].Id, &modelClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.Save(inbounds)
|
||||||
|
|
||||||
|
// Remove orphaned traffics
|
||||||
|
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -28,6 +29,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
|
"sessionMaxAge": "0",
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
@@ -234,18 +236,10 @@ func (s *SettingService) GetTgBotBackup() (bool, error) {
|
|||||||
return s.getBool("tgBotBackup")
|
return s.getBool("tgBotBackup")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetTgBotBackup(value bool) error {
|
|
||||||
return s.setBool("tgBotBackup", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTgCpu() (int, error) {
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetTgCpu(value int) error {
|
|
||||||
return s.setInt("tgCpu", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
@@ -266,16 +260,12 @@ func (s *SettingService) GetExpireDiff() (int, error) {
|
|||||||
return s.getInt("expireDiff")
|
return s.getInt("expireDiff")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetExpireDiff(value int) error {
|
|
||||||
return s.setInt("expireDiff", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SettingService) GetTrafficDiff() (int, error) {
|
func (s *SettingService) GetTrafficDiff() (int, error) {
|
||||||
return s.getInt("trafficDiff")
|
return s.getInt("trafficDiff")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) SetgetTrafficDiff(value int) error {
|
func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||||
return s.setInt("trafficDiff", value)
|
return s.getInt("sessionMaxAge")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
@@ -337,3 +327,12 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
|||||||
}
|
}
|
||||||
return common.Combine(errs...)
|
return common.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||||
|
var jsonData interface{}
|
||||||
|
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,13 +66,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header = fmt.Sprintf("upload=%d;download=%d", traffic.Up, traffic.Down)
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
if traffic.Total > 0 {
|
|
||||||
header = header + fmt.Sprintf(";total=%d", traffic.Total)
|
|
||||||
}
|
|
||||||
if traffic.ExpiryTime > 0 {
|
|
||||||
header = header + fmt.Sprintf(";expire=%d", traffic.ExpiryTime)
|
|
||||||
}
|
|
||||||
return result, header, nil
|
return result, header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,85 +97,96 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
|||||||
return s.genVlessLink(inbound, email)
|
return s.genVlessLink(inbound, email)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email)
|
||||||
|
case "shadowsocks":
|
||||||
|
return s.genShadowsocksLink(inbound, email)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
address := s.address
|
|
||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
obj := map[string]interface{}{
|
||||||
|
"v": "2",
|
||||||
|
"ps": email,
|
||||||
|
"add": s.address,
|
||||||
|
"port": inbound.Port,
|
||||||
|
"type": "none",
|
||||||
|
}
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
network, _ := stream["network"].(string)
|
network, _ := stream["network"].(string)
|
||||||
typeStr := "none"
|
obj["net"] = network
|
||||||
host := ""
|
|
||||||
path := ""
|
|
||||||
sni := ""
|
|
||||||
fp := ""
|
|
||||||
var alpn []string
|
|
||||||
allowInsecure := false
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
header, _ := tcp["header"].(map[string]interface{})
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
typeStr, _ = header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
|
obj["type"] = typeStr
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]interface{})
|
request := header["request"].(map[string]interface{})
|
||||||
requestPath, _ := request["path"].([]interface{})
|
requestPath, _ := request["path"].([]interface{})
|
||||||
path = requestPath[0].(string)
|
obj["path"] = requestPath[0].(string)
|
||||||
headers, _ := request["headers"].(map[string]interface{})
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
host = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "kcp":
|
case "kcp":
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
header, _ := kcp["header"].(map[string]interface{})
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
typeStr, _ = header["type"].(string)
|
obj["type"], _ = header["type"].(string)
|
||||||
path, _ = kcp["seed"].(string)
|
obj["path"], _ = kcp["seed"].(string)
|
||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
path = ws["path"].(string)
|
obj["path"] = ws["path"].(string)
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
host = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
case "http":
|
case "http":
|
||||||
network = "h2"
|
obj["net"] = "h2"
|
||||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
path, _ = http["path"].(string)
|
obj["path"], _ = http["path"].(string)
|
||||||
host = searchHost(http)
|
obj["host"] = searchHost(http)
|
||||||
case "quic":
|
case "quic":
|
||||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
header := quic["header"].(map[string]interface{})
|
header := quic["header"].(map[string]interface{})
|
||||||
typeStr, _ = header["type"].(string)
|
obj["type"], _ = header["type"].(string)
|
||||||
host, _ = quic["security"].(string)
|
obj["host"], _ = quic["security"].(string)
|
||||||
path, _ = quic["key"].(string)
|
obj["path"], _ = quic["key"].(string)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
path = grpc["serviceName"].(string)
|
obj["path"] = grpc["serviceName"].(string)
|
||||||
|
if grpc["multiMode"].(bool) {
|
||||||
|
obj["type"] = "multi"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||||
for _, a := range alpns {
|
if len(alpns) > 0 {
|
||||||
alpn = append(alpn, a.(string))
|
var alpn []string
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
obj["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||||
sni, _ = sniValue.(string)
|
obj["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
fp, _ = fpValue.(string)
|
obj["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
allowInsecure, _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
if serverName != "" {
|
if serverName != "" {
|
||||||
address = serverName
|
obj["add"] = serverName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,24 +198,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
obj["id"] = clients[clientIndex].ID
|
||||||
|
obj["aid"] = clients[clientIndex].AlterIds
|
||||||
|
|
||||||
obj := map[string]interface{}{
|
|
||||||
"v": "2",
|
|
||||||
"ps": email,
|
|
||||||
"add": address,
|
|
||||||
"port": inbound.Port,
|
|
||||||
"id": clients[clientIndex].ID,
|
|
||||||
"aid": clients[clientIndex].AlterIds,
|
|
||||||
"net": network,
|
|
||||||
"type": typeStr,
|
|
||||||
"host": host,
|
|
||||||
"path": path,
|
|
||||||
"tls": security,
|
|
||||||
"sni": sni,
|
|
||||||
"fp": fp,
|
|
||||||
"alpn": strings.Join(alpn, ","),
|
|
||||||
"allowInsecure": allowInsecure,
|
|
||||||
}
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
@@ -272,6 +262,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
if grpc["multiMode"].(bool) {
|
||||||
|
params["mode"] = "multi"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
@@ -421,6 +414,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
if grpc["multiMode"].(bool) {
|
||||||
|
params["mode"] = "multi"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
@@ -510,6 +506,28 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
||||||
|
address := s.address
|
||||||
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
clients, _ := s.inboundService.getClients(inbound)
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
inboundPassword := settings["password"].(string)
|
||||||
|
method := settings["method"].(string)
|
||||||
|
clientIndex := -1
|
||||||
|
for i, client := range clients {
|
||||||
|
if client.Email == email {
|
||||||
|
clientIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
|
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email)
|
||||||
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
switch val := data.(type) {
|
switch val := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
@@ -538,7 +556,11 @@ func searchHost(headers interface{}) string {
|
|||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
hosts, _ := v.([]interface{})
|
hosts, _ := v.([]interface{})
|
||||||
return hosts[0].(string)
|
if len(hosts) > 0 {
|
||||||
|
return hosts[0].(string)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
case interface{}:
|
case interface{}:
|
||||||
return v.(string)
|
return v.(string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -404,38 +404,36 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||||
traffics, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
msg := "❌ Something went wrong!"
|
msg := "❌ Something went wrong!"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(traffics) == 0 {
|
if traffic == nil {
|
||||||
msg := "No result!"
|
msg := "No result!"
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, traffic := range traffics {
|
expiryTime := ""
|
||||||
expiryTime := ""
|
if traffic.ExpiryTime == 0 {
|
||||||
if traffic.ExpiryTime == 0 {
|
expiryTime = "♾Unlimited"
|
||||||
expiryTime = "♾Unlimited"
|
} else if traffic.ExpiryTime < 0 {
|
||||||
} else if traffic.ExpiryTime < 0 {
|
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
} else {
|
||||||
} else {
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
total := ""
|
|
||||||
if traffic.Total == 0 {
|
|
||||||
total = "♾Unlimited"
|
|
||||||
} else {
|
|
||||||
total = common.FormatTraffic((traffic.Total))
|
|
||||||
}
|
|
||||||
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
|
||||||
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
|
||||||
total, expiryTime)
|
|
||||||
t.SendMsgToTgbot(chatId, output)
|
|
||||||
}
|
}
|
||||||
|
total := ""
|
||||||
|
if traffic.Total == 0 {
|
||||||
|
total = "♾Unlimited"
|
||||||
|
} else {
|
||||||
|
total = common.FormatTraffic((traffic.Total))
|
||||||
|
}
|
||||||
|
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
||||||
|
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||||
|
total, expiryTime)
|
||||||
|
t.SendMsgToTgbot(chatId, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.inboundService.DisableInvalidClients()
|
s.inboundService.DisableInvalidClients()
|
||||||
s.inboundService.RemoveOrphanedTraffics()
|
|
||||||
|
|
||||||
inbounds, err := s.inboundService.GetAllInbounds()
|
inbounds, err := s.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,12 +118,15 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
||||||
delete(c, key)
|
delete(c, key)
|
||||||
}
|
}
|
||||||
|
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||||
|
c["flow"] = "xtls-rprx-vision"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final_clients = append(final_clients, interface{}(c))
|
final_clients = append(final_clients, interface{}(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
settings["clients"] = final_clients
|
settings["clients"] = final_clients
|
||||||
modifiedSettings, err := json.Marshal(settings)
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package session
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"x-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"x-ui/database/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,6 +22,15 @@ func SetLoginUser(c *gin.Context, user *model.User) error {
|
|||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||||
|
s := sessions.Default(c)
|
||||||
|
s.Options(sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: maxAge,
|
||||||
|
})
|
||||||
|
return s.Save()
|
||||||
|
}
|
||||||
|
|
||||||
func GetLoginUser(c *gin.Context) *model.User {
|
func GetLoginUser(c *gin.Context) *model.User {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
obj := s.Get(loginUser)
|
obj := s.Get(loginUser)
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"expireDate" = "Expire date"
|
"expireDate" = "Expire date"
|
||||||
"resetTraffic" = "Reset traffic"
|
"resetTraffic" = "Reset traffic"
|
||||||
"addInbound" = "Add Inbound"
|
"addInbound" = "Add Inbound"
|
||||||
|
"generalActions" = "General Actions"
|
||||||
"addTo" = "Create"
|
"addTo" = "Create"
|
||||||
"revise" = "Update"
|
"revise" = "Update"
|
||||||
"modifyInbound" = "Modify InBound"
|
"modifyInbound" = "Modify InBound"
|
||||||
@@ -138,9 +139,15 @@
|
|||||||
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
"resetAllTrafficContent" = "Are you sure to reset all inbounds traffic ?"
|
||||||
"resetAllClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
|
"resetInboundClientTrafficTitle" = "Reset all clients traffic"
|
||||||
|
"resetInboundClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
||||||
|
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
||||||
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of all clients ?"
|
||||||
|
"delDepletedClients" = "Delete depleted clients"
|
||||||
|
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||||
|
"delDepletedClientsContent" = "Are you sure to delete all depleted clients ?"
|
||||||
"Email" = "Email"
|
"Email" = "Email"
|
||||||
"EmailDesc" = "The Email Must Be Completely Unique"
|
"EmailDesc" = "The Email Must Be Completely Unique"
|
||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
@@ -186,6 +193,7 @@
|
|||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
"restartPanel" = "Restart Panel"
|
"restartPanel" = "Restart Panel"
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
||||||
|
"resetDefaultConfig" = "Reset to default config"
|
||||||
"panelConfig" = "Panel Configuration"
|
"panelConfig" = "Panel Configuration"
|
||||||
"userSetting" = "User Setting"
|
"userSetting" = "User Setting"
|
||||||
"xrayConfiguration" = "xray Configuration"
|
"xrayConfiguration" = "xray Configuration"
|
||||||
@@ -199,7 +207,7 @@
|
|||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"publicKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"privateKeyPath" = "Panel certificate private key file path"
|
"privateKeyPath" = "Panel certificate private key file path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
"privateKeyPathDesc" = "Fill in an absolute path starting with '/', restart the panel to take effect"
|
||||||
"panelUrlPath" = "panel url root path"
|
"panelUrlPath" = "Panel url root path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
"panelUrlPathDesc" = "Must start with '/' and end with '/', restart the panel to take effect"
|
||||||
"oldUsername" = "Current Username"
|
"oldUsername" = "Current Username"
|
||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
@@ -229,6 +237,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
||||||
"tgNotifyBackup" = "Database backup"
|
"tgNotifyBackup" = "Database backup"
|
||||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
||||||
|
"sessionMaxAge" = "Session maximum age"
|
||||||
|
"sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
|
||||||
"expireTimeDiff" = "Exhaustion time threshold"
|
"expireTimeDiff" = "Exhaustion time threshold"
|
||||||
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
||||||
"trafficDiff" = "Exhaustion traffic threshold"
|
"trafficDiff" = "Exhaustion traffic threshold"
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"expireDate" = "تاریخ انقضا"
|
"expireDate" = "تاریخ انقضا"
|
||||||
"resetTraffic" = "ریست ترافیک"
|
"resetTraffic" = "ریست ترافیک"
|
||||||
"addInbound" = "اضافه کردن سرویس"
|
"addInbound" = "اضافه کردن سرویس"
|
||||||
|
"generalActions" = "عملیات کلی"
|
||||||
"addTo" = "اضافه کردن"
|
"addTo" = "اضافه کردن"
|
||||||
"revise" = "ویرایش"
|
"revise" = "ویرایش"
|
||||||
"modifyInbound" = "ویرایش سرویس"
|
"modifyInbound" = "ویرایش سرویس"
|
||||||
@@ -138,9 +139,15 @@
|
|||||||
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
||||||
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
||||||
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
||||||
|
"resetInboundClientTraffics" = "ریست ترافیک کاربران"
|
||||||
|
"resetInboundClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||||
|
"resetInboundClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||||
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
||||||
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||||
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران را ریست کنید؟"
|
||||||
|
"delDepletedClients" = "حذف کاربران منقضی"
|
||||||
|
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||||
|
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||||
"Email" = "ایمیل"
|
"Email" = "ایمیل"
|
||||||
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
"EmailDesc" = "ایمیل باید کاملا منحصر به فرد باشد"
|
||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
@@ -186,6 +193,7 @@
|
|||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelConfig" = "تنظیمات پنل"
|
||||||
"userSetting" = "تنظیمات مدیر"
|
"userSetting" = "تنظیمات مدیر"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
@@ -229,6 +237,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
|
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||||
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"expireDate" = "到期时间"
|
"expireDate" = "到期时间"
|
||||||
"resetTraffic" = "重置流量"
|
"resetTraffic" = "重置流量"
|
||||||
"addInbound" = "添加入"
|
"addInbound" = "添加入"
|
||||||
|
"generalActions" = "通用操作"
|
||||||
"addTo" = "添加"
|
"addTo" = "添加"
|
||||||
"revise" = "修改"
|
"revise" = "修改"
|
||||||
"modifyInbound" = "修改入站"
|
"modifyInbound" = "修改入站"
|
||||||
@@ -138,9 +139,15 @@
|
|||||||
"resetAllTraffic" = "重置所有入站流量"
|
"resetAllTraffic" = "重置所有入站流量"
|
||||||
"resetAllTrafficTitle" = "重置所有入站流量"
|
"resetAllTrafficTitle" = "重置所有入站流量"
|
||||||
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
||||||
"resetAllClientTraffics" = "重置客户端流量"
|
"resetInboundClientTraffics" = "重置客户端流量"
|
||||||
|
"resetInboundClientTrafficTitle" = "重置所有客户端流量"
|
||||||
|
"resetInboundClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||||
|
"resetAllClientTraffics" = "重置所有客户端流量"
|
||||||
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
||||||
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
"resetAllClientTrafficContent" = "你确定要重置所有客户端的所有流量吗?"
|
||||||
|
"delDepletedClients" = "删除耗尽的客户端"
|
||||||
|
"delDepletedClientsTitle" = "删除耗尽的客户"
|
||||||
|
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
||||||
"Email" = "电子邮件"
|
"Email" = "电子邮件"
|
||||||
"EmailDesc" = "电子邮件必须完全唯"
|
"EmailDesc" = "电子邮件必须完全唯"
|
||||||
"setDefaultCert" = "从面板设置证书"
|
"setDefaultCert" = "从面板设置证书"
|
||||||
@@ -186,6 +193,7 @@
|
|||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
"restartPanel" = "重启面板"
|
"restartPanel" = "重启面板"
|
||||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||||
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelConfig" = "面板配置"
|
"panelConfig" = "面板配置"
|
||||||
"userSetting" = "用户设置"
|
"userSetting" = "用户设置"
|
||||||
"xrayConfiguration" = "xray 相关设置"
|
"xrayConfiguration" = "xray 相关设置"
|
||||||
@@ -229,6 +237,8 @@
|
|||||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||||
"tgNotifyBackup" = "数据库备份"
|
"tgNotifyBackup" = "数据库备份"
|
||||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||||
|
"sessionMaxAge" = "会话最大年龄"
|
||||||
|
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
||||||
"expireTimeDiff" = "耗尽时间阈值"
|
"expireTimeDiff" = "耗尽时间阈值"
|
||||||
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||||
"trafficDiff" = "耗尽流量阈值"
|
"trafficDiff" = "耗尽流量阈值"
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ import (
|
|||||||
//go:embed assets/*
|
//go:embed assets/*
|
||||||
var assetsFS embed.FS
|
var assetsFS embed.FS
|
||||||
|
|
||||||
//go:embed assets/favicon.ico
|
|
||||||
var favicon []byte
|
|
||||||
|
|
||||||
//go:embed html/*
|
//go:embed html/*
|
||||||
var htmlFS embed.FS
|
var htmlFS embed.FS
|
||||||
|
|
||||||
@@ -161,11 +158,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
// Add favicon
|
|
||||||
engine.GET("/favicon.ico", func(c *gin.Context) {
|
|
||||||
c.Data(200, "image/x-icon", favicon)
|
|
||||||
})
|
|
||||||
|
|
||||||
secret, err := s.settingService.GetSecret()
|
secret, err := s.settingService.GetSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user