mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-19 15:25:49 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c5bb94894 | ||
|
|
39c1a4276d | ||
|
|
4736786c6f | ||
|
|
402c713f06 | ||
|
|
a371bec2aa | ||
|
|
427d008bd1 | ||
|
|
e69d17be67 | ||
|
|
09c61976ea | ||
|
|
6e17c282e0 | ||
|
|
700973655c | ||
|
|
dcb54267f2 | ||
|
|
19e851d5c8 | ||
|
|
3f7ef07b8e | ||
|
|
89be2c8fec | ||
|
|
dd11585074 | ||
|
|
7ecb73af8c | ||
|
|
ba083ecc7e | ||
|
|
365ec1a704 | ||
|
|
63969d5fd6 | ||
|
|
0449a35409 |
11
README.md
11
README.md
@@ -54,13 +54,18 @@ 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` | `"/:id/delClient/:clientId"` | Delete Client by UID/Password as clientId |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update Client by UID/Password as clientId |
|
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
|
||||||
|
*- The field `clientId` should be filled by:
|
||||||
|
- `client.id` for VMESS and VLESS
|
||||||
|
- `client.password` for TROJAN
|
||||||
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
| Variable | Type | Default |
|
| Variable | Type | Default |
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.1.0
|
1.2.0
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1270,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) {
|
||||||
@@ -1332,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1392,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
|
||||||
@@ -1849,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={}) {
|
||||||
@@ -1864,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)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1872,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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 != "" {
|
||||||
@@ -85,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
<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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
if (this.clients[index].expiryTime < 0){
|
if (this.clients[index].expiryTime < 0){
|
||||||
this.delayedStart = true;
|
this.delayedStart = true;
|
||||||
}
|
}
|
||||||
this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id;
|
this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]);
|
||||||
} else {
|
} else {
|
||||||
this.addClient(this.inbound.protocol, this.clients);
|
this.addClient(this.inbound.protocol, this.clients);
|
||||||
}
|
}
|
||||||
@@ -55,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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -137,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}}
|
||||||
@@ -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,7 +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"
|
||||||
|
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>
|
||||||
|
|||||||
@@ -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,28 +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-dropdown :trigger="['click']">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-menu-item key="export">
|
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||||
<a-icon type="export"></a-icon>
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||||
{{ i18n "pages.inbounds.export" }}
|
<a-menu-item key="export">
|
||||||
</a-menu-item>
|
<a-icon type="export"></a-icon>
|
||||||
<a-menu-item key="resetInbounds">
|
{{ i18n "pages.inbounds.export" }}
|
||||||
<a-icon type="reload"></a-icon>
|
</a-menu-item>
|
||||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
<a-menu-item key="resetInbounds">
|
||||||
</a-menu-item>
|
<a-icon type="reload"></a-icon>
|
||||||
<a-menu-item key="resetClients">
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
<a-icon type="file-done"></a-icon>
|
</a-menu-item>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
<a-menu-item key="resetClients">
|
||||||
</a-menu-item>
|
<a-icon type="file-done"></a-icon>
|
||||||
<a-menu-item key="delDepletedClients">
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
<a-icon type="rest"></a-icon>
|
</a-menu-item>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
<a-menu-item key="delDepletedClients">
|
||||||
</a-menu-item>
|
<a-icon type="rest"></a-icon>
|
||||||
</a-menu>
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-dropdown>
|
</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"
|
||||||
@@ -100,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"}}
|
||||||
@@ -152,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>
|
||||||
@@ -214,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)"
|
||||||
@@ -312,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;
|
||||||
}
|
}
|
||||||
@@ -342,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 = [];
|
||||||
@@ -654,7 +660,7 @@
|
|||||||
},
|
},
|
||||||
delClient(dbInboundId,client) {
|
delClient(dbInboundId,client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = dbInbound.protocol == "trojan" ? client.password : client.id;
|
clientId = clientId = getClientId(dbInbound.protocol,client);;
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
@@ -669,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);
|
||||||
@@ -690,7 +704,7 @@
|
|||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id;
|
clientId = getClientId(dbInbound.protocol,clients[index]);
|
||||||
await this.updateClient(clients[index],dbInboundId, clientId);
|
await this.updateClient(clients[index],dbInboundId, clientId);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
@@ -702,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) {
|
||||||
@@ -785,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) {
|
||||||
@@ -792,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() {
|
||||||
@@ -820,7 +869,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
|
|||||||
@@ -113,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>
|
||||||
@@ -201,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();
|
||||||
|
|||||||
@@ -318,6 +318,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
|||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
client_key = "password"
|
client_key = "password"
|
||||||
}
|
}
|
||||||
|
if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
client_key = "email"
|
||||||
|
}
|
||||||
|
|
||||||
inerfaceClients := settings["clients"].([]interface{})
|
inerfaceClients := settings["clients"].([]interface{})
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
@@ -378,6 +381,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
if oldInbound.Protocol == "trojan" {
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
|
} else if oldInbound.Protocol == "shadowsocks" {
|
||||||
|
oldClientId = oldClient.Email
|
||||||
} else {
|
} else {
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
}
|
}
|
||||||
@@ -528,7 +533,7 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
|||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
for traffic_index := range dbClientTraffics {
|
for traffic_index := range dbClientTraffics {
|
||||||
if c["email"] == dbClientTraffics[traffic_index].Email {
|
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||||
oldExpiryTime := c["expiryTime"].(float64)
|
oldExpiryTime := c["expiryTime"].(float64)
|
||||||
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
||||||
c["expiryTime"] = newExpiryTime
|
c["expiryTime"] = newExpiryTime
|
||||||
@@ -792,12 +797,14 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
|||||||
|
|
||||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", 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[0], 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) {
|
||||||
@@ -851,6 +858,8 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
|||||||
|
|
||||||
func (s *InboundService) MigrationRequirements() {
|
func (s *InboundService) MigrationRequirements() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
// Fix inbounds based problems
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
@@ -861,6 +870,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
clients, ok := settings["clients"].([]interface{})
|
clients, ok := settings["clients"].([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
|
// Fix Clinet configuration problems
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
for client_index := range clients {
|
for client_index := range clients {
|
||||||
c := clients[client_index].(map[string]interface{})
|
c := clients[client_index].(map[string]interface{})
|
||||||
@@ -886,6 +896,8 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
|
|
||||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add client traffic row for all clients which has email
|
||||||
modelClients, err := s.getClients(inbounds[inbound_index])
|
modelClients, err := s.getClients(inbounds[inbound_index])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -901,4 +913,7 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.Save(inbounds)
|
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"
|
||||||
@@ -326,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
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,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)
|
||||||
}
|
}
|
||||||
@@ -266,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)
|
||||||
@@ -415,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)
|
||||||
@@ -504,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{}:
|
||||||
|
|||||||
@@ -193,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"
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
|
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||||
"panelConfig" = "تنظیمات پنل"
|
"panelConfig" = "تنظیمات پنل"
|
||||||
"userSetting" = "تنظیمات مدیر"
|
"userSetting" = "تنظیمات مدیر"
|
||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
"restartPanel" = "重启面板"
|
"restartPanel" = "重启面板"
|
||||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||||
|
"resetDefaultConfig" = "重置为默认配置"
|
||||||
"panelConfig" = "面板配置"
|
"panelConfig" = "面板配置"
|
||||||
"userSetting" = "用户设置"
|
"userSetting" = "用户设置"
|
||||||
"xrayConfiguration" = "xray 相关设置"
|
"xrayConfiguration" = "xray 相关设置"
|
||||||
|
|||||||
@@ -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