mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-20 05:52:24 +00:00
improve reality setting
split xtls from tls - remove iran warp - remove old setting reality from franzkafka (it was a messy code) -and other improvement Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
This commit is contained in:
parent
dc7dbae14a
commit
3e0faecaae
20 changed files with 635 additions and 332 deletions
|
@ -246,6 +246,11 @@
|
||||||
background-color: #2e3b52;
|
background-color: #2e3b52;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
background-color: #242c3a;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-collapse-item {
|
.ant-card-dark .ant-collapse-item {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #161b22;
|
background-color: #161b22;
|
||||||
|
|
|
@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = {
|
||||||
|
|
||||||
const TLS_FLOW_CONTROL = {
|
const TLS_FLOW_CONTROL = {
|
||||||
VISION: "xtls-rprx-vision",
|
VISION: "xtls-rprx-vision",
|
||||||
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TLS_VERSION_OPTION = {
|
const TLS_VERSION_OPTION = {
|
||||||
|
@ -91,9 +92,6 @@ const UTLS_FINGERPRINT = {
|
||||||
UTLS_RANDOMIZED: "randomized",
|
UTLS_RANDOMIZED: "randomized",
|
||||||
};
|
};
|
||||||
|
|
||||||
const bytesToHex = e => Array.from(e).map(e => e.toString(16).padStart(2, 0)).join('');
|
|
||||||
const hexToBytes = e => new Uint8Array(e.match(/[0-9a-f]{2}/gi).map(e => parseInt(e, 16)));
|
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
H3: "h3",
|
H3: "h3",
|
||||||
H2: "h2",
|
H2: "h2",
|
||||||
|
@ -481,7 +479,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
settings=[new TlsStreamSettings.Settings()]) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.server = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
|
@ -508,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
let values = json.settings[0];
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
|
@ -530,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: TlsStreamSettings.toJsonArray(this.settings),
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -598,71 +595,204 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class XtlsStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(serverName='',
|
||||||
|
certificates=[new XtlsStreamSettings.Cert()],
|
||||||
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
|
settings=new XtlsStreamSettings.Settings()) {
|
||||||
|
super();
|
||||||
|
this.server = serverName;
|
||||||
|
this.certs = certificates;
|
||||||
|
this.alpn = alpn;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCert(cert) {
|
||||||
|
this.certs.push(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCert(index) {
|
||||||
|
this.certs.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let certs;
|
||||||
|
let settings;
|
||||||
|
if (!ObjectUtil.isEmpty(json.certificates)) {
|
||||||
|
certs = json.certificates.map(cert => XtlsStreamSettings.Cert.fromJson(cert));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
settings = new XtlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.serverName);
|
||||||
|
}
|
||||||
|
return new XtlsStreamSettings(
|
||||||
|
json.serverName,
|
||||||
|
certs,
|
||||||
|
json.alpn,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serverName: this.server,
|
||||||
|
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
||||||
|
alpn: this.alpn,
|
||||||
|
settings: this.settings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XtlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
|
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
||||||
|
super();
|
||||||
|
this.useFile = useFile;
|
||||||
|
this.certFile = certificateFile;
|
||||||
|
this.keyFile = keyFile;
|
||||||
|
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||||
|
this.key = key instanceof Array ? key.join('\n') : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if ('certificateFile' in json && 'keyFile' in json) {
|
||||||
|
return new XtlsStreamSettings.Cert(
|
||||||
|
true,
|
||||||
|
json.certificateFile,
|
||||||
|
json.keyFile,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new XtlsStreamSettings.Cert(
|
||||||
|
false, '', '',
|
||||||
|
json.certificate.join('\n'),
|
||||||
|
json.key.join('\n'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
if (this.useFile) {
|
||||||
|
return {
|
||||||
|
certificateFile: this.certFile,
|
||||||
|
keyFile: this.keyFile,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
certificate: this.cert.split('\n'),
|
||||||
|
key: this.key.split('\n'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(allowInsecure = false, serverName = '') {
|
||||||
|
super();
|
||||||
|
this.allowInsecure = allowInsecure;
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new XtlsStreamSettings.Settings(
|
||||||
|
json.allowInsecure,
|
||||||
|
json.servername,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
allowInsecure: this.allowInsecure,
|
||||||
|
serverName: this.serverName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class RealityStreamSettings extends XrayCommonClass {
|
class RealityStreamSettings extends XrayCommonClass {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
show = false,xver = 0,
|
show = false,xver = 0,
|
||||||
fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX,
|
|
||||||
dest = 'yahoo.com:443',
|
dest = 'yahoo.com:443',
|
||||||
serverNames = 'yahoo.com,www.yahoo.com',
|
serverNames = 'yahoo.com,www.yahoo.com',
|
||||||
privateKey = RandomUtil.randomX25519PrivateKey(),
|
privateKey = '',
|
||||||
publicKey = '',
|
|
||||||
minClient = '',
|
minClient = '',
|
||||||
maxClient = '',
|
maxClient = '',
|
||||||
maxTimediff = 0,
|
maxTimediff = 0,
|
||||||
shortIds = RandomUtil.randowShortId()
|
shortIds = RandomUtil.randowShortId(),
|
||||||
)
|
settings= new RealityStreamSettings.Settings()
|
||||||
{
|
){
|
||||||
super();
|
super();
|
||||||
this.show = show;
|
this.show = show;
|
||||||
this.xver = xver;
|
this.xver = xver;
|
||||||
this.fingerprint = fingerprint;
|
|
||||||
this.dest = dest;
|
this.dest = dest;
|
||||||
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey);
|
|
||||||
this.minClient = minClient;
|
this.minClient = minClient;
|
||||||
this.maxClient = maxClient;
|
this.maxClient = maxClient;
|
||||||
this.maxTimediff = maxTimediff;
|
this.maxTimediff = maxTimediff;
|
||||||
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
||||||
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
let settings;
|
||||||
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
|
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
|
||||||
|
}
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
json.show,
|
json.show,
|
||||||
json.xver,
|
json.xver,
|
||||||
json.fingerprint,
|
|
||||||
json.dest,
|
json.dest,
|
||||||
json.serverNames,
|
json.serverNames,
|
||||||
json.privateKey,
|
json.privateKey,
|
||||||
json.publicKey,
|
|
||||||
json.minClient,
|
json.minClient,
|
||||||
json.maxClient,
|
json.maxClient,
|
||||||
json.maxTimediff,
|
json.maxTimediff,
|
||||||
json.shortIds
|
json.shortIds,
|
||||||
|
json.settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
show: this.show,
|
show: this.show,
|
||||||
xver: this.xver,
|
xver: this.xver,
|
||||||
fingerprint: this.fingerprint,
|
|
||||||
dest: this.dest,
|
dest: this.dest,
|
||||||
serverNames: this.serverNames.split(/,|,|\s+/),
|
serverNames: this.serverNames.split(","),
|
||||||
privateKey: this.privateKey,
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
|
||||||
minClient: this.minClient,
|
minClient: this.minClient,
|
||||||
maxClient: this.maxClient,
|
maxClient: this.maxClient,
|
||||||
maxTimediff: this.maxTimediff,
|
maxTimediff: this.maxTimediff,
|
||||||
shortIds: this.shortIds.split(/,|,|\s+/)
|
shortIds: this.shortIds.split(","),
|
||||||
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') {
|
||||||
|
super();
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
}
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new RealityStreamSettings.Settings(
|
||||||
|
json.publicKey,
|
||||||
|
json.fingerprint,
|
||||||
|
json.serverName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
tlsSettings=new TlsStreamSettings(),
|
tlsSettings=new TlsStreamSettings(),
|
||||||
|
xtlsSettings=new XtlsStreamSettings(),
|
||||||
realitySettings = new RealityStreamSettings(),
|
realitySettings = new RealityStreamSettings(),
|
||||||
tcpSettings=new TcpStreamSettings(),
|
tcpSettings=new TcpStreamSettings(),
|
||||||
kcpSettings=new KcpStreamSettings(),
|
kcpSettings=new KcpStreamSettings(),
|
||||||
|
@ -675,6 +805,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
|
this.xtls = xtlsSettings;
|
||||||
this.reality = realitySettings;
|
this.reality = realitySettings;
|
||||||
this.tcp = tcpSettings;
|
this.tcp = tcpSettings;
|
||||||
this.kcp = kcpSettings;
|
this.kcp = kcpSettings;
|
||||||
|
@ -685,7 +816,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
return this.security === 'tls';
|
return this.security === "tls";
|
||||||
}
|
}
|
||||||
|
|
||||||
set isTls(isTls) {
|
set isTls(isTls) {
|
||||||
|
@ -696,12 +827,12 @@ class StreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isXTLS() {
|
get isXtls() {
|
||||||
return this.security === "xtls";
|
return this.security === "xtls";
|
||||||
}
|
}
|
||||||
|
|
||||||
set isXTLS(isXTLS) {
|
set isXtls(isXtls) {
|
||||||
if (isXTLS) {
|
if (isXtls) {
|
||||||
this.security = 'xtls';
|
this.security = 'xtls';
|
||||||
} else {
|
} else {
|
||||||
this.security = 'none';
|
this.security = 'none';
|
||||||
|
@ -715,27 +846,19 @@ class StreamSettings extends XrayCommonClass {
|
||||||
|
|
||||||
set isReality(isReality) {
|
set isReality(isReality) {
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
this.security = "reality";
|
this.security = 'reality';
|
||||||
} else {
|
} else {
|
||||||
this.security = "none";
|
this.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json={}) {
|
||||||
let tls, reality;
|
|
||||||
if (json.security === "xtls") {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.XTLSSettings);
|
|
||||||
} else if (json.security === "tls") {
|
|
||||||
tls = TlsStreamSettings.fromJson(json.tlsSettings);
|
|
||||||
}
|
|
||||||
if (json.security === "reality") {
|
|
||||||
reality = RealityStreamSettings.fromJson(json.realitySettings)
|
|
||||||
}
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
json.security,
|
json.security,
|
||||||
tls,
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
reality,
|
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||||
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
WsStreamSettings.fromJson(json.wsSettings),
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
|
@ -751,9 +874,9 @@ class StreamSettings extends XrayCommonClass {
|
||||||
network: network,
|
network: network,
|
||||||
security: this.security,
|
security: this.security,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
|
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
|
||||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
|
@ -826,22 +949,18 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
set tls(isTls) {
|
set tls(isTls) {
|
||||||
if (isTls) {
|
if (isTls) {
|
||||||
this.xtls = false;
|
|
||||||
this.reality = false;
|
|
||||||
this.stream.security = 'tls';
|
this.stream.security = 'tls';
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = 'none';
|
this.stream.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get XTLS() {
|
get xtls() {
|
||||||
return this.stream.security === 'xtls';
|
return this.stream.security === 'xtls';
|
||||||
}
|
}
|
||||||
|
|
||||||
set XTLS(isXTLS) {
|
set xtls(isXtls) {
|
||||||
if (isXTLS) {
|
if (isXtls) {
|
||||||
this.xtls = false;
|
|
||||||
this.reality = false;
|
|
||||||
this.stream.security = 'xtls';
|
this.stream.security = 'xtls';
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = 'none';
|
this.stream.security = 'none';
|
||||||
|
@ -850,19 +969,14 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
//for Reality
|
//for Reality
|
||||||
get reality() {
|
get reality() {
|
||||||
if (this.stream.security === "reality") {
|
return this.stream.security === 'reality';
|
||||||
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set reality(isReality) {
|
set reality(isReality) {
|
||||||
if (isReality) {
|
if (isReality) {
|
||||||
this.tls = false;
|
this.stream.security = 'reality';
|
||||||
this.xtls = false;
|
|
||||||
this.stream.security = "reality";
|
|
||||||
} else {
|
} else {
|
||||||
this.stream.security = "none";
|
this.stream.security = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -969,7 +1083,7 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXTLS) {
|
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
||||||
return this.stream.tls.server;
|
return this.stream.tls.server;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
@ -1070,7 +1184,14 @@ class Inbound extends XrayCommonClass {
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
|
switch (this.network) {
|
||||||
|
case "tcp":
|
||||||
|
case "http":
|
||||||
|
case "grpc":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
|
@ -1090,7 +1211,7 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.canEnableTls();
|
return this.canEnableTls();
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableXTLS() {
|
canEnableXtls() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
|
@ -1195,10 +1316,10 @@ class Inbound extends XrayCommonClass {
|
||||||
host: host,
|
host: host,
|
||||||
path: path,
|
path: path,
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
sni: this.stream.tls.settings[0]['serverName'],
|
sni: this.stream.tls.settings.serverName,
|
||||||
fp: this.stream.tls.settings[0]['fingerprint'],
|
fp: this.stream.tls.settings.fingerprint,
|
||||||
alpn: this.stream.tls.alpn.join(','),
|
alpn: this.stream.tls.alpn.join(','),
|
||||||
allowInsecure: this.stream.tls.settings[0].allowInsecure,
|
allowInsecure: this.stream.tls.settings.allowInsecure,
|
||||||
};
|
};
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
|
@ -1257,51 +1378,51 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
|
||||||
if (this.stream.reality.publicKey != "") {
|
|
||||||
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
|
|
||||||
params.set("pbk", this.stream.reality.publicKey);
|
|
||||||
}
|
}
|
||||||
if (this.stream.network === 'tcp') {
|
if (this.stream.network === 'tcp') {
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds != "") {
|
if (this.stream.reality.shortIds != "") {
|
||||||
params.set("sid", this.stream.reality.shortIds);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.fingerprint != "") {
|
if (this.stream.reality.settings.fingerprint != "") {
|
||||||
params.set("fp", this.stream.reality.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1376,47 +1497,47 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
if (this.tls) {
|
if (this.tls) {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings[0]['fingerprint']);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
}
|
}
|
||||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
if (this.stream.tls.settings.serverName !== ''){
|
||||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
params.set("sni", this.stream.tls.settings.serverName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.publicKey != "") {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
|
address = this.stream.reality.settings.serverName;
|
||||||
params.set("pbk", this.stream.reality.publicKey);
|
|
||||||
}
|
|
||||||
if (this.stream.network === 'tcp') {
|
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds != "") {
|
if (this.stream.reality.shortIds != "") {
|
||||||
params.set("sid", this.stream.reality.shortIds);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.fingerprint != "") {
|
if (this.stream.reality.settings.fingerprint != "") {
|
||||||
params.set("fp", this.stream.reality.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.XTLS) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.tls.settings[0].allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.xtls.server;
|
||||||
}
|
}
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,26 +94,6 @@ const shortIdSeq = [
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
];
|
];
|
||||||
|
|
||||||
const x25519Map = new Map(
|
|
||||||
[
|
|
||||||
['EH2FWe-Ij_FFAa2u9__-aiErLvVIneP601GOCdlyPWw', "goY3OtfaA4UYbiz7Hn0NysI5QJrK0VT_Chg6RLgUPQU"],
|
|
||||||
['cKI_6DoMSP1IepeWWXrG3G9nkehl94KYBhagU50g2U0', "VigpKFbSLnHLzBWobZaS1IBmw--giJ51w92y723ajnU"],
|
|
||||||
['qM2SNyK3NyHB6deWpEP3ITyCGKQFRTna_mlKP0w1QH0', "HYyIGuyNFslmcnNT7mrDdmuXwn4cm7smE_FZbYguKHQ"],
|
|
||||||
['qCWg5GMEDFd3n1nxDswlIpOHoPUXMLuMOIiLUVzubkI', "rJFC3dUjJxMnVZiUGzmf_LFsJUwFWY-CU5RQgFOHCWM"],
|
|
||||||
['4NOBxDrEsOhNI3Y3EnVIy_TN-uyBoAjQw6QM0YsOi0s', "CbcY9qc4YuMDJDyyL0OITlU824TBg1O84ClPy27e2RM"],
|
|
||||||
['eBvFb0M4HpSOwWjtXV8zliiEs_hg56zX4a2LpuuqpEI', "CjulQ2qVIky7ImIfysgQhNX7s_drGLheCGSkVHcLZhc"],
|
|
||||||
['yEpOzQV04NNcycWVeWtRNTzv5TS-ynTuKRacZCH-6U8', "O9RSr5gSdok2K_tobQnf_scyKVqnCx6C4Jrl7_rCZEQ"],
|
|
||||||
['CNt6TAUVCwqM6xIBHyni0K3Zqbn2htKQLvLb6XDgh0s', "d9cGLVBrDFS02L2OvkqyqwFZ1Ux3AHs28ehl4Rwiyl0"],
|
|
||||||
['EInKw-6Wr0rAHXlxxDuZU5mByIzcD3Z-_iWPzXlUL1k', "LlYD2nNVAvyjNvjZGZh4R8PkMIwkc6EycPTvR2LE0nQ"],
|
|
||||||
['GKIKo7rcXVyle-EUHtGIDtYnDsI6osQmOUl3DTJRAGc', "VcqHivYGGoBkcxOI6cSSjQmneltstkb2OhvO53dyhEM"],
|
|
||||||
['-FVDzv68IC17fJVlNDlhrrgX44WeBfbhwjWpCQVXGHE', "PGG2EYOvsFt2lAQTD7lqHeRxz2KxvllEDKcUrtizPBU"],
|
|
||||||
['0H3OJEYEu6XW7woqy7cKh2vzg6YHkbF_xSDTHKyrsn4', "mzevpYbS8kXengBY5p7tt56QE4tS3lwlwRemmkcQeyc"],
|
|
||||||
['8F8XywN6ci44ES6em2Z0fYYxyptB9uaXY9Hc1WSSPE4', "qCZUdWQZ2H33vWXnOkG8NpxBeq3qn5QWXlfCOWBNkkc"],
|
|
||||||
['IN0dqfkC10dj-ifRHrg2PmmOrzYs697ajGMwcLbu-1g', "2UW_EO3r7uczPGUUlpJBnMDpDmWUHE2yDzCmXS4sckE"],
|
|
||||||
['uIcmks5rAhvBe4dRaJOdeSqgxLGGMZhsGk4J4PEKL2s', "F9WJV_74IZp0Ide4hWjiJXk9FRtBUBkUr3mzU-q1lzk"],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
|
|
||||||
static randomIntRange(min, max) {
|
static randomIntRange(min, max) {
|
||||||
|
@ -170,26 +150,6 @@ class RandomUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static randowShortId() {
|
|
||||||
let str = '';
|
|
||||||
str += this.randomShortIdSeq(8)
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomX25519PrivateKey() {
|
|
||||||
let num = x25519Map.size;
|
|
||||||
let index = this.randomInt(num);
|
|
||||||
let cntr = 0;
|
|
||||||
for (let key of x25519Map.keys()) {
|
|
||||||
if (cntr++ === index) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomX25519PublicKey(key) {
|
|
||||||
return x25519Map.get(key)
|
|
||||||
}
|
|
||||||
static randomText() {
|
static randomText() {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
|
@ -199,6 +159,12 @@ class RandomUtil {
|
||||||
}
|
}
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static randowShortId() {
|
||||||
|
let str = '';
|
||||||
|
str += this.randomShortIdSeq(8)
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectUtil {
|
class ObjectUtil {
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
g.POST("/clientIps/:email", a.getClientIps)
|
g.POST("/clientIps/:email", a.getClientIps)
|
||||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||||
g.POST("/addClient/", a.addInboundClient)
|
g.POST("/addClient", a.addInboundClient)
|
||||||
g.POST("/delClient/:email", a.delInboundClient)
|
g.POST("/delClient/:email", a.delInboundClient)
|
||||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
@ -151,19 +151,19 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
}
|
}
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(data)
|
||||||
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.AddInboundClient(inbound)
|
err = a.inboundService.AddInboundClient(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "something worng!", err)
|
jsonMsg(c, "something worng!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
g.POST("/getConfigJson", a.getConfigJson)
|
||||||
g.GET("/getDb", a.getDb)
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
|
@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
count := c.Param("count")
|
count := c.Param("count")
|
||||||
logs, err := a.serverService.GetLogs(count)
|
logs, err := a.serverService.GetLogs(count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "getLogs", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
|
@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||||
configJson, err := a.serverService.GetConfigJson()
|
configJson, err := a.serverService.GetConfigJson()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "get config.json", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, configJson, nil)
|
jsonObj(c, configJson, nil)
|
||||||
|
@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||||
func (a *ServerController) getDb(c *gin.Context) {
|
func (a *ServerController) getDb(c *gin.Context) {
|
||||||
db, err := a.serverService.GetDb()
|
db, err := a.serverService.GetDb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
jsonMsg(c, "get Database", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Set the headers for the response
|
// Set the headers for the response
|
||||||
|
@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) {
|
||||||
// Write the file contents to the response
|
// Write the file contents to the response
|
||||||
c.Writer.Write(db)
|
c.Writer.Write(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||||
|
cert, err := a.serverService.GetNewX25519Cert()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get x25519 certificate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, cert, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -29,14 +29,18 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, err := a.subService.GetSubs(subId, host)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
result := ""
|
result := ""
|
||||||
for _, sub := range subs {
|
for _, sub := range subs {
|
||||||
result += sub + "\n"
|
result += sub + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add subscription-userinfo
|
||||||
|
c.Writer.Header().Set("subscription-userinfo", header)
|
||||||
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,30 @@
|
||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<span slot="label">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<a-input type="number" v-model.number="clientsBulkModal.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
||||||
|
<a-select v-model="clientsBulkModal.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
||||||
|
<a-select v-model="clientsBulkModal.flow" style="width: 150px">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="Subscription">
|
<a-form-item label="Subscription">
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -51,10 +75,10 @@
|
||||||
</span>
|
</span>
|
||||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
||||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
|
@ -83,9 +107,9 @@
|
||||||
confirm: null,
|
confirm: null,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
totalGB: 0,
|
totalGB: 0,
|
||||||
|
limitIp: 0,
|
||||||
expiryTime: '',
|
expiryTime: '',
|
||||||
emailMethod: 0,
|
emailMethod: 0,
|
||||||
firstNum: 1,
|
firstNum: 1,
|
||||||
|
@ -94,8 +118,10 @@
|
||||||
emailPostfix: "",
|
emailPostfix: "",
|
||||||
subId: "",
|
subId: "",
|
||||||
tgId: "",
|
tgId: "",
|
||||||
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
|
clients = [];
|
||||||
method=clientsBulkModal.emailMethod;
|
method=clientsBulkModal.emailMethod;
|
||||||
if(method>1){
|
if(method>1){
|
||||||
start=clientsBulkModal.firstNum;
|
start=clientsBulkModal.firstNum;
|
||||||
|
@ -113,11 +139,18 @@
|
||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
newClient.subId = clientsBulkModal.subId;
|
newClient.subId = clientsBulkModal.subId;
|
||||||
newClient.tgId = clientsBulkModal.tgId;
|
newClient.tgId = clientsBulkModal.tgId;
|
||||||
|
newClient.limitIp = clientsBulkModal.limitIp;
|
||||||
newClient._totalGB = clientsBulkModal.totalGB;
|
newClient._totalGB = clientsBulkModal.totalGB;
|
||||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||||
clientsBulkModal.clients.push(newClient);
|
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||||
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound);
|
if(clientsBulkModal.inbound.xtls){
|
||||||
|
newClient.flow = clientsBulkModal.flow;
|
||||||
|
}
|
||||||
|
clients.push(newClient);
|
||||||
|
}
|
||||||
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
@ -128,15 +161,16 @@
|
||||||
this.totalGB = 0;
|
this.totalGB = 0;
|
||||||
this.expiryTime = 0;
|
this.expiryTime = 0;
|
||||||
this.emailMethod= 0;
|
this.emailMethod= 0;
|
||||||
|
this.limitIp= 0;
|
||||||
this.firstNum= 1;
|
this.firstNum= 1;
|
||||||
this.lastNum= 1;
|
this.lastNum= 1;
|
||||||
this.emailPrefix= "";
|
this.emailPrefix= "";
|
||||||
this.emailPostfix= "";
|
this.emailPostfix= "";
|
||||||
this.subId= "";
|
this.subId= "";
|
||||||
this.tgId= "";
|
this.tgId= "";
|
||||||
|
this.flow= "";
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
getClients(protocol, clientSettings) {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
title: '',
|
title: '',
|
||||||
okText: '',
|
okText: '',
|
||||||
|
isEdit: false,
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
clients: [],
|
clients: [],
|
||||||
|
@ -21,9 +22,13 @@
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
ObjectUtil.execute(clientModal.confirm, clientModal.inbound, clientModal.dbInbound, clientModal.index);
|
if(clientModal.isEdit){
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
|
||||||
|
} else {
|
||||||
|
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=(index, dbInbound)=>{}, isEdit=false }) {
|
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.okText = okText;
|
this.okText = okText;
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
</a-textarea>
|
</a-textarea>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.XTLS" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
@ -100,10 +100,10 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.delayedStart" }}">
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
|
||||||
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/trojan"}}
|
{{define "form/trojan"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.XTLS" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</span>
|
</span>
|
||||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.XTLS" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header="{{ i18n "pages.inbounds.client" }}">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.reality"></a-switch>
|
<a-switch v-model="inbound.reality"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.canEnableXTLS()">
|
<a-form-item v-if="inbound.canEnableXtls()">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
XTLS
|
XTLS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
@ -27,14 +27,14 @@
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-switch v-model="inbound.XTLS"></a-switch>
|
<a-switch v-model="inbound.xtls"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls || inbound.XTLS" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CipherSuites">
|
<a-form-item label="CipherSuites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px">
|
||||||
|
@ -52,22 +52,22 @@
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS">
|
||||||
|
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 170px">
|
||||||
<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>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.server"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label="Alpn">
|
||||||
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
||||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
</a-checkbox-group>
|
</a-checkbox-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||||
|
@ -93,33 +93,79 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
|
<!-- xtls settings -->
|
||||||
|
<a-form v-if="inbound.xtls" layout="inline">
|
||||||
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Alpn">
|
||||||
|
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
|
||||||
|
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Allow insecure">
|
||||||
|
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
|
<a-radio-group v-model="inbound.stream.xtls.certs[0].useFile" button-style="solid">
|
||||||
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="inbound.stream.xtls.certs[0].useFile">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.certs[0].certFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- reality settings -->
|
||||||
<a-form v-else-if="inbound.reality" layout="inline">
|
<a-form v-else-if="inbound.reality" layout="inline">
|
||||||
<a-form-item label="show">
|
<a-form-item label="Show">
|
||||||
<a-switch v-model="inbound.stream.reality.show">
|
<a-switch v-model="inbound.stream.reality.show">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xver">
|
<a-form-item label="xVer">
|
||||||
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
<a-input type="number" v-model.number="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS" >
|
<a-form-item label="uTLS" >
|
||||||
<a-select v-model="inbound.stream.reality.fingerprint" style="width: 135px">
|
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 135px">
|
||||||
<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 label='{{ i18n "domainName" }}'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="dest">
|
<a-form-item label="dest">
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 360px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="serverNames">
|
<a-form-item label="Server Names">
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 360px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="privateKey">
|
<a-form-item label="ShortIds">
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 360px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="publicKey">
|
|
||||||
<a-input v-model.trim="inbound.stream.reality.publicKey" style="width: 360px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="shortIds">
|
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Private Key">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Public Key">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item >
|
||||||
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
|
@ -49,10 +49,14 @@
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.XTLS">
|
<td v-else-if="inbound.xtls">
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-else-if="inbound.reality">
|
||||||
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
|
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
|
</td>
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -43,6 +43,14 @@
|
||||||
loading(loading) {
|
loading(loading) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
|
getClients(protocol, clientSettings) {
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.VMESS: return clientSettings.vmesses;
|
||||||
|
case Protocols.VLESS: return clientSettings.vlesses;
|
||||||
|
case Protocols.TROJAN: return clientSettings.trojans;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const protocols = {
|
const protocols = {
|
||||||
|
@ -62,6 +70,7 @@
|
||||||
inModal: inModal,
|
inModal: inModal,
|
||||||
Protocols: protocols,
|
Protocols: protocols,
|
||||||
SSMethods: SSMethods,
|
SSMethods: SSMethods,
|
||||||
|
delayedStart: false,
|
||||||
get inbound() {
|
get inbound() {
|
||||||
return inModal.inbound;
|
return inModal.inbound;
|
||||||
},
|
},
|
||||||
|
@ -70,36 +79,40 @@
|
||||||
},
|
},
|
||||||
get isEdit() {
|
get isEdit() {
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
}
|
},
|
||||||
|
get client() {
|
||||||
|
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
|
||||||
|
},
|
||||||
|
get delayedExpireDays() {
|
||||||
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
},
|
||||||
|
set delayedExpireDays(days){
|
||||||
|
this.client.expiryTime = -86400000 * days;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange(oldValue) {
|
streamNetworkChange() {
|
||||||
if (oldValue === 'kcp') {
|
if (!inModal.inbound.canSetTls()) {
|
||||||
this.inModal.inbound.tls = false;
|
this.inModal.inbound.stream.security = 'none';
|
||||||
}
|
}
|
||||||
},
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
addClient(protocol, clients) {
|
this.inModal.inbound.reality = false;
|
||||||
switch (protocol) {
|
|
||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
|
||||||
default: return null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeClient(index, clients) {
|
|
||||||
clients.splice(index, 1);
|
|
||||||
},
|
|
||||||
isExpiry(index) {
|
|
||||||
return this.inbound.isExpiry(index)
|
|
||||||
},
|
|
||||||
isClientEnable(email) {
|
|
||||||
clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null
|
|
||||||
return clientStats ? clientStats['enable'] : true
|
|
||||||
},
|
|
||||||
setDefaultCertData(){
|
setDefaultCertData(){
|
||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
|
async getNewX25519Cert(){
|
||||||
|
inModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||||
|
inModal.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
|
},
|
||||||
getNewEmail(client) {
|
getNewEmail(client) {
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||||
var string = '';
|
var string = '';
|
||||||
|
|
|
@ -133,26 +133,26 @@
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<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.isXTLS" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</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>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title="{{ i18n "disabled" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depleted" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title="{{ i18n "depletingSoon" }}" :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="siderDrawer.isDarkTheme ? 'ant-dark' : ''">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
|
@ -531,9 +531,9 @@
|
||||||
title: '{{ i18n "pages.client.add"}}',
|
title: '{{ i18n "pages.client.add"}}',
|
||||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: false
|
isEdit: false
|
||||||
|
@ -545,9 +545,9 @@
|
||||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||||
okText: '{{ i18n "pages.client.bulk"}}',
|
okText: '{{ i18n "pages.client.bulk"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
confirm: async (inbound, dbInbound) => {
|
confirm: async (clients, dbInboundId) => {
|
||||||
clientsBulkModal.loading();
|
clientsBulkModal.loading();
|
||||||
await this.addClient(inbound, dbInbound);
|
await this.addClient(clients, dbInboundId);
|
||||||
clientsBulkModal.close();
|
clientsBulkModal.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -561,9 +561,9 @@
|
||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
dbInbound: dbInbound,
|
dbInbound: dbInbound,
|
||||||
index: index,
|
index: index,
|
||||||
confirm: async (inbound, dbInbound, index) => {
|
confirm: async (client, dbInboundId, index) => {
|
||||||
clientModal.loading();
|
clientModal.loading();
|
||||||
await this.updateClient(inbound, dbInbound, index);
|
await this.updateClient(client, dbInboundId, index);
|
||||||
clientModal.close();
|
clientModal.close();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
|
@ -573,17 +573,17 @@
|
||||||
firstKey = Object.keys(client)[0];
|
firstKey = Object.keys(client)[0];
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||||
},
|
},
|
||||||
async addClient(inbound, dbInbound) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + clients.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit('/xui/inbound/addClient/', data);
|
await this.submit(`/xui/inbound/addClient`, data);
|
||||||
},
|
},
|
||||||
async updateClient(inbound, dbInbound, index) {
|
async updateClient(client, dbInboundId, index) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInbound.id,
|
id: dbInboundId,
|
||||||
settings: inbound.settings.toString(),
|
settings: '{"clients": [' + client.toString() +']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
||||||
},
|
},
|
||||||
|
|
|
@ -125,7 +125,6 @@
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.setting.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigOpenAIWARP"}}' desc='{{ i18n "pages.setting.xrayConfigOpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigNetflixWARP"}}' desc='{{ i18n "pages.setting.xrayConfigNetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigSpotifyWARP"}}' desc='{{ i18n "pages.setting.xrayConfigSpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.setting.xrayConfigIRWARP"}}' desc='{{ i18n "pages.setting.xrayConfigIRWARPDesc"}}' v-model="IRWARPSettings"></setting-list-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
|
|
||||||
|
@ -672,23 +671,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IRWARPSettings: {
|
|
||||||
get: function () {
|
|
||||||
return this.templateRuleGetter({
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set: function (newValue) {
|
|
||||||
this.templateRuleSetter({
|
|
||||||
newValue,
|
|
||||||
outboundTag: "WARP",
|
|
||||||
property: "domain",
|
|
||||||
data: this.settingsData.domains.ir
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err
|
||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var emails []string
|
||||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
err := db.Raw(`
|
||||||
if ignoreId > 0 {
|
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||||
db = db.Where("id != ?", ignoreId)
|
FROM inbounds,
|
||||||
}
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
db = db.Find(&inbounds)
|
`).Scan(&emails).Error
|
||||||
if db.Error != nil {
|
|
||||||
return "", db.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, inbound := range inbounds {
|
if err != nil {
|
||||||
clients, err := s.getClients(inbound)
|
return nil, err
|
||||||
|
}
|
||||||
|
return emails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) contains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||||
|
allEmails, err := s.getAllEmails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
var emails []string
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if emails[client.Email] {
|
if client.Email != "" {
|
||||||
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
emails := make(map[string]bool)
|
allEmails, err := s.getAllEmails()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var emails []string
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email != "" {
|
if client.Email != "" {
|
||||||
if emails[client.Email] {
|
if s.contains(emails, client.Email) {
|
||||||
return client.Email, nil
|
return client.Email, nil
|
||||||
}
|
}
|
||||||
emails[client.Email] = true
|
if s.contains(allEmails, client.Email) {
|
||||||
|
return client.Email, nil
|
||||||
|
}
|
||||||
|
emails = append(emails, client.Email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.checkEmailsExist(emails, inbound.Id)
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) {
|
||||||
|
@ -215,14 +239,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||||
return inbound, common.NewError("Port already exists:", inbound.Port)
|
return inbound, common.NewError("Port already exists:", inbound.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
|
||||||
if err != nil {
|
|
||||||
return inbound, err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return inbound, common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(inbound.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, err
|
return inbound, err
|
||||||
|
@ -245,8 +261,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||||
return inbound, db.Save(oldInbound).Error
|
return inbound, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -255,29 +275,35 @@ func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClients, err := s.getClients(oldInbound)
|
oldClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
for _, client := range clients {
|
||||||
|
newClients = append(newClients, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = append(oldClients, newClients...)
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
if len(clients[len(clients)-1].Email) > 0 {
|
for _, client := range clients {
|
||||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
if len(client.Email) > 0 {
|
||||||
}
|
s.AddClientStat(data.Id, &client)
|
||||||
for i := len(oldClients); i < len(clients); i++ {
|
|
||||||
if len(clients[i].Email) > 0 {
|
|
||||||
s.AddClientStat(inbound.Id, &clients[i])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
@ -309,21 +335,13 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
|
||||||
return db.Save(oldInbound).Error
|
return db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
clients, err := s.getClients(data)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existEmail != "" {
|
|
||||||
return common.NewError("Duplicate email:", existEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, err := s.getClients(inbound)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(inbound.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -333,13 +351,40 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldInbound.Settings = inbound.Settings
|
if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
|
||||||
|
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existEmail != "" {
|
||||||
|
return common.NewError("Duplicate email:", existEmail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsClients := settings["clients"].([]interface{})
|
||||||
|
var newClients []interface{}
|
||||||
|
newClients = append(newClients, clients[0])
|
||||||
|
settingsClients[index] = newClients[0]
|
||||||
|
|
||||||
|
settings["clients"] = settingsClients
|
||||||
|
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbound.Settings = string(newSettings)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
if len(clients[index].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldClients[index].Email) > 0 {
|
if len(oldClients[index].Email) > 0 {
|
||||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[index])
|
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -348,7 +393,7 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.AddClientStat(inbound.Id, &clients[index])
|
s.AddClientStat(data.Id, &clients[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = s.DelClientStat(db, oldClients[index].Email)
|
err = s.DelClientStat(db, oldClients[index].Email)
|
||||||
|
@ -507,6 +552,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
|
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
||||||
|
Update("enable", false)
|
||||||
|
err := result.Error
|
||||||
|
count := result.RowsAffected
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
func (s *InboundService) RemoveOrphanedTraffics() {
|
func (s *InboundService) RemoveOrphanedTraffics() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
|
@ -518,16 +573,6 @@ func (s *InboundService) RemoveOrphanedTraffics() {
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
func (s *InboundService) DisableInvalidClients() (int64, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
now := time.Now().Unix() * 1000
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true).
|
|
||||||
Update("enable", false)
|
|
||||||
err := result.Error
|
|
||||||
count := result.RowsAffected
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
|
|
@ -390,3 +390,29 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
||||||
|
|
||||||
return fileContents, nil
|
return fileContents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
privateKeyLine := strings.Split(lines[0], ":")
|
||||||
|
publicKeyLine := strings.Split(lines[1], ":")
|
||||||
|
|
||||||
|
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||||
|
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||||
|
|
||||||
|
keyPair := map[string]interface{}{
|
||||||
|
"privateKey": privateKey,
|
||||||
|
"publicKey": publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
@ -18,12 +19,15 @@ type SubService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
||||||
s.address = host
|
s.address = host
|
||||||
var result []string
|
var result []string
|
||||||
|
var header string
|
||||||
|
var traffic xray.ClientTraffic
|
||||||
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.getClients(inbound)
|
clients, err := s.inboundService.getClients(inbound)
|
||||||
|
@ -37,22 +41,60 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
||||||
if client.SubID == subId {
|
if client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email)
|
||||||
result = append(result, link)
|
result = append(result, link)
|
||||||
|
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
for index, clientTraffic := range clientTraffics {
|
||||||
|
if index == 0 {
|
||||||
|
traffic.Up = clientTraffic.Up
|
||||||
|
traffic.Down = clientTraffic.Down
|
||||||
|
traffic.Total = clientTraffic.Total
|
||||||
|
if clientTraffic.ExpiryTime > 0 {
|
||||||
|
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traffic.Up += clientTraffic.Up
|
||||||
|
traffic.Down += clientTraffic.Down
|
||||||
|
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
||||||
|
traffic.Total = 0
|
||||||
|
} else {
|
||||||
|
traffic.Total += clientTraffic.Total
|
||||||
|
}
|
||||||
|
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
||||||
|
traffic.ExpiryTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header = fmt.Sprintf("upload=%d;download=%d", traffic.Up, traffic.Down)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic {
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
if traffic.Email == email {
|
||||||
|
return traffic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xray.ClientTraffic{}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
|
@ -296,7 +338,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
|
|
||||||
if security == "xtls" {
|
if security == "xtls" {
|
||||||
params["security"] = "xtls"
|
params["security"] = "xtls"
|
||||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
|
@ -306,15 +348,15 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
params["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
if insecure.(bool) {
|
if insecure.(bool) {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
|
@ -465,7 +507,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
|
|
||||||
if security == "xtls" {
|
if security == "xtls" {
|
||||||
params["security"] = "xtls"
|
params["security"] = "xtls"
|
||||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||||
var alpn []string
|
var alpn []string
|
||||||
for _, a := range alpns {
|
for _, a := range alpns {
|
||||||
|
@ -475,15 +517,15 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
params["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||||
if insecure.(bool) {
|
if insecure.(bool) {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue