mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-03-01 01:20:49 +03: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
@ -246,6 +246,11 @@
|
||||
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 {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #161b22;
|
||||
|
@ -49,6 +49,7 @@ const XTLS_FLOW_CONTROL = {
|
||||
|
||||
const TLS_FLOW_CONTROL = {
|
||||
VISION: "xtls-rprx-vision",
|
||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||
};
|
||||
|
||||
const TLS_VERSION_OPTION = {
|
||||
@ -91,9 +92,6 @@ const UTLS_FINGERPRINT = {
|
||||
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 = {
|
||||
H3: "h3",
|
||||
H2: "h2",
|
||||
@ -481,7 +479,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites = '',
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
settings=[new TlsStreamSettings.Settings()]) {
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.server = serverName;
|
||||
this.minVersion = minVersion;
|
||||
@ -508,8 +506,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
let values = json.settings[0];
|
||||
settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)];
|
||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
||||
}
|
||||
return new TlsStreamSettings(
|
||||
json.serverName,
|
||||
@ -530,7 +527,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites: this.cipherSuites,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
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 {
|
||||
|
||||
constructor(
|
||||
show = false,xver = 0,
|
||||
fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX,
|
||||
dest = 'yahoo.com:443',
|
||||
serverNames = 'yahoo.com,www.yahoo.com',
|
||||
privateKey = RandomUtil.randomX25519PrivateKey(),
|
||||
publicKey = '',
|
||||
privateKey = '',
|
||||
minClient = '',
|
||||
maxClient = '',
|
||||
maxTimediff = 0,
|
||||
shortIds = RandomUtil.randowShortId()
|
||||
)
|
||||
{
|
||||
shortIds = RandomUtil.randowShortId(),
|
||||
settings= new RealityStreamSettings.Settings()
|
||||
){
|
||||
super();
|
||||
this.show = show;
|
||||
this.xver = xver;
|
||||
this.fingerprint = fingerprint;
|
||||
this.dest = dest;
|
||||
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = RandomUtil.randomX25519PublicKey(this.privateKey);
|
||||
this.minClient = minClient;
|
||||
this.maxClient = maxClient;
|
||||
this.maxTimediff = maxTimediff;
|
||||
this.shortIds = shortIds instanceof Array ? shortIds.join(",") : shortIds;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
let settings;
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
return new RealityStreamSettings(
|
||||
json.show,
|
||||
json.xver,
|
||||
json.fingerprint,
|
||||
json.dest,
|
||||
json.serverNames,
|
||||
json.privateKey,
|
||||
json.publicKey,
|
||||
json.minClient,
|
||||
json.maxClient,
|
||||
json.maxTimediff,
|
||||
json.shortIds
|
||||
json.shortIds,
|
||||
json.settings,
|
||||
);
|
||||
}
|
||||
toJson() {
|
||||
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
show: this.show,
|
||||
xver: this.xver,
|
||||
fingerprint: this.fingerprint,
|
||||
dest: this.dest,
|
||||
serverNames: this.serverNames.split(/,|,|\s+/),
|
||||
serverNames: this.serverNames.split(","),
|
||||
privateKey: this.privateKey,
|
||||
publicKey: this.publicKey,
|
||||
minClient: this.minClient,
|
||||
maxClient: this.maxClient,
|
||||
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 {
|
||||
constructor(network='tcp',
|
||||
security='none',
|
||||
tlsSettings=new TlsStreamSettings(),
|
||||
xtlsSettings=new XtlsStreamSettings(),
|
||||
realitySettings = new RealityStreamSettings(),
|
||||
tcpSettings=new TcpStreamSettings(),
|
||||
kcpSettings=new KcpStreamSettings(),
|
||||
@ -675,6 +805,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.network = network;
|
||||
this.security = security;
|
||||
this.tls = tlsSettings;
|
||||
this.xtls = xtlsSettings;
|
||||
this.reality = realitySettings;
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
@ -685,7 +816,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get isTls() {
|
||||
return this.security === 'tls';
|
||||
return this.security === "tls";
|
||||
}
|
||||
|
||||
set isTls(isTls) {
|
||||
@ -696,12 +827,12 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
get isXTLS() {
|
||||
get isXtls() {
|
||||
return this.security === "xtls";
|
||||
}
|
||||
|
||||
set isXTLS(isXTLS) {
|
||||
if (isXTLS) {
|
||||
set isXtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.security = 'xtls';
|
||||
} else {
|
||||
this.security = 'none';
|
||||
@ -715,27 +846,19 @@ class StreamSettings extends XrayCommonClass {
|
||||
|
||||
set isReality(isReality) {
|
||||
if (isReality) {
|
||||
this.security = "reality";
|
||||
this.security = 'reality';
|
||||
} else {
|
||||
this.security = "none";
|
||||
this.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
static fromJson(json={}) {
|
||||
return new StreamSettings(
|
||||
json.network,
|
||||
json.security,
|
||||
tls,
|
||||
reality,
|
||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||
RealityStreamSettings.fromJson(json.realitySettings),
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
@ -751,9 +874,9 @@ class StreamSettings extends XrayCommonClass {
|
||||
network: network,
|
||||
security: this.security,
|
||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||
XTLSSettings: this.isXTLS ? this.tls.toJson() : undefined,
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
@ -826,22 +949,18 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
set tls(isTls) {
|
||||
if (isTls) {
|
||||
this.xtls = false;
|
||||
this.reality = false;
|
||||
this.stream.security = 'tls';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
get XTLS() {
|
||||
get xtls() {
|
||||
return this.stream.security === 'xtls';
|
||||
}
|
||||
|
||||
set XTLS(isXTLS) {
|
||||
if (isXTLS) {
|
||||
this.xtls = false;
|
||||
this.reality = false;
|
||||
set xtls(isXtls) {
|
||||
if (isXtls) {
|
||||
this.stream.security = 'xtls';
|
||||
} else {
|
||||
this.stream.security = 'none';
|
||||
@ -850,19 +969,14 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
//for Reality
|
||||
get reality() {
|
||||
if (this.stream.security === "reality") {
|
||||
return this.network === "tcp" || this.network === "grpc" || this.network === "http";
|
||||
}
|
||||
return false;
|
||||
return this.stream.security === 'reality';
|
||||
}
|
||||
|
||||
set reality(isReality) {
|
||||
if (isReality) {
|
||||
this.tls = false;
|
||||
this.xtls = false;
|
||||
this.stream.security = "reality";
|
||||
this.stream.security = 'reality';
|
||||
} else {
|
||||
this.stream.security = "none";
|
||||
this.stream.security = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
@ -969,7 +1083,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
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 "";
|
||||
@ -1070,7 +1184,14 @@ class Inbound extends XrayCommonClass {
|
||||
default:
|
||||
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
|
||||
@ -1090,7 +1211,7 @@ class Inbound extends XrayCommonClass {
|
||||
return this.canEnableTls();
|
||||
}
|
||||
|
||||
canEnableXTLS() {
|
||||
canEnableXtls() {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
@ -1195,10 +1316,10 @@ class Inbound extends XrayCommonClass {
|
||||
host: host,
|
||||
path: path,
|
||||
tls: this.stream.security,
|
||||
sni: this.stream.tls.settings[0]['serverName'],
|
||||
fp: this.stream.tls.settings[0]['fingerprint'],
|
||||
sni: this.stream.tls.settings.serverName,
|
||||
fp: this.stream.tls.settings.fingerprint,
|
||||
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));
|
||||
}
|
||||
@ -1257,51 +1378,51 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
if (this.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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.XTLS) {
|
||||
if (this.xtls) {
|
||||
params.set("security", "xtls");
|
||||
params.set("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if(this.stream.xtls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
||||
address = this.stream.xtls.server;
|
||||
}
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||
params.set("sni", this.stream.reality.serverNames.split(/,|,|\s+/)[0]);
|
||||
}
|
||||
if (this.stream.reality.publicKey != "") {
|
||||
//params.set("pbk", Ed25519.getPublicKey(this.stream.reality.privateKey));
|
||||
params.set("pbk", this.stream.reality.publicKey);
|
||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||
}
|
||||
if (this.stream.network === 'tcp') {
|
||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||
}
|
||||
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 != "") {
|
||||
params.set("fp", this.stream.reality.fingerprint);
|
||||
if (this.stream.reality.settings.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) {
|
||||
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);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
if(this.stream.tls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
if (this.stream.tls.settings[0]['serverName'] !== ''){
|
||||
params.set("sni", this.stream.tls.settings[0]['serverName']);
|
||||
if (this.stream.tls.settings.serverName !== ''){
|
||||
params.set("sni", this.stream.tls.settings.serverName);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.reality) {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
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') {
|
||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||
address = this.stream.reality.settings.serverName;
|
||||
}
|
||||
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 != "") {
|
||||
params.set("fp", this.stream.reality.fingerprint);
|
||||
if (this.stream.reality.settings.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("alpn", this.stream.tls.alpn);
|
||||
if(this.stream.tls.settings[0].allowInsecure){
|
||||
params.set("alpn", this.stream.xtls.alpn);
|
||||
if(this.stream.xtls.settings.allowInsecure){
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -94,26 +94,6 @@ const shortIdSeq = [
|
||||
'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 {
|
||||
|
||||
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() {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
@ -199,6 +159,12 @@ class RandomUtil {
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
static randowShortId() {
|
||||
let str = '';
|
||||
str += this.randomShortIdSeq(8)
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectUtil {
|
||||
|
@ -33,7 +33,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update/:id", a.updateInbound)
|
||||
g.POST("/clientIps/:email", a.getClientIps)
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/addClient/", a.addInboundClient)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/delClient/:email", a.delInboundClient)
|
||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
@ -151,19 +151,19 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
||||
jsonMsg(c, "Log Cleared", nil)
|
||||
}
|
||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
inbound := &model.Inbound{}
|
||||
err := c.ShouldBind(inbound)
|
||||
data := &model.Inbound{}
|
||||
err := c.ShouldBind(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.inboundService.AddInboundClient(inbound)
|
||||
err = a.inboundService.AddInboundClient(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client added", nil)
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/logs/:count", a.getLogs)
|
||||
g.POST("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
}
|
||||
|
||||
func (a *ServerController) refreshStatus() {
|
||||
@ -114,7 +115,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logs, err := a.serverService.GetLogs(count)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
||||
jsonMsg(c, "getLogs", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, logs, nil)
|
||||
@ -123,7 +124,7 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||
configJson, err := a.serverService.GetConfigJson()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
||||
jsonMsg(c, "get config.json", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, configJson, nil)
|
||||
@ -132,7 +133,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||
func (a *ServerController) getDb(c *gin.Context) {
|
||||
db, err := a.serverService.GetDb()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "getLogs"), err)
|
||||
jsonMsg(c, "get Database", err)
|
||||
return
|
||||
}
|
||||
// Set the headers for the response
|
||||
@ -142,3 +143,12 @@ func (a *ServerController) getDb(c *gin.Context) {
|
||||
// Write the file contents to the response
|
||||
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) {
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
subs, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil {
|
||||
subs, header, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
result := ""
|
||||
for _, sub := range subs {
|
||||
result += sub + "\n"
|
||||
}
|
||||
|
||||
// Add subscription-userinfo
|
||||
c.Writer.Header().Set("subscription-userinfo", header)
|
||||
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,30 @@
|
||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</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-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||
</a-form-item>
|
||||
@ -51,10 +75,10 @@
|
||||
</span>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</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-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-form-item>
|
||||
<a-form-item v-else>
|
||||
@ -83,9 +107,9 @@
|
||||
confirm: null,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
quantity: 1,
|
||||
totalGB: 0,
|
||||
limitIp: 0,
|
||||
expiryTime: '',
|
||||
emailMethod: 0,
|
||||
firstNum: 1,
|
||||
@ -94,8 +118,10 @@
|
||||
emailPostfix: "",
|
||||
subId: "",
|
||||
tgId: "",
|
||||
flow: "",
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
clients = [];
|
||||
method=clientsBulkModal.emailMethod;
|
||||
if(method>1){
|
||||
start=clientsBulkModal.firstNum;
|
||||
@ -113,11 +139,18 @@
|
||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||
newClient.subId = clientsBulkModal.subId;
|
||||
newClient.tgId = clientsBulkModal.tgId;
|
||||
newClient.limitIp = clientsBulkModal.limitIp;
|
||||
newClient._totalGB = clientsBulkModal.totalGB;
|
||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||
clientsBulkModal.clients.push(newClient);
|
||||
if(clientsBulkModal.inbound.canEnableTlsFlow()){
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
if(clientsBulkModal.inbound.xtls){
|
||||
newClient.flow = clientsBulkModal.flow;
|
||||
}
|
||||
clients.push(newClient);
|
||||
}
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clientsBulkModal.inbound, clientsBulkModal.dbInbound);
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||
this.visible = true;
|
||||
@ -128,15 +161,16 @@
|
||||
this.totalGB = 0;
|
||||
this.expiryTime = 0;
|
||||
this.emailMethod= 0;
|
||||
this.limitIp= 0;
|
||||
this.firstNum= 1;
|
||||
this.lastNum= 1;
|
||||
this.emailPrefix= "";
|
||||
this.emailPostfix= "";
|
||||
this.subId= "";
|
||||
this.tgId= "";
|
||||
this.flow= "";
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
confirmLoading: false,
|
||||
title: '',
|
||||
okText: '',
|
||||
isEdit: false,
|
||||
dbInbound: new DBInbound(),
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
@ -21,9 +22,13 @@
|
||||
isExpired: false,
|
||||
delayedStart: false,
|
||||
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.title = title;
|
||||
this.okText = okText;
|
||||
|
@ -68,7 +68,7 @@
|
||||
</a-textarea>
|
||||
</a-form>
|
||||
</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-option value="">{{ i18n "none" }}</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>
|
||||
</template>
|
||||
</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-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-form-item>
|
||||
<a-form-item v-else>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{{define "form/trojan"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
@ -31,7 +31,7 @@
|
||||
</span>
|
||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||
</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-option value="">{{ i18n "none" }}</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"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
@ -31,7 +31,7 @@
|
||||
</span>
|
||||
<a-input type="number" v-model.number="client.limitIp" min="0" style="width: 70px;" ></a-input>
|
||||
</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-option value="" selected>{{ i18n "none" }}</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"}}
|
||||
<a-form layout="inline">
|
||||
<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-item>
|
||||
<span slot="label">
|
||||
|
@ -17,7 +17,7 @@
|
||||
</span>
|
||||
<a-switch v-model="inbound.reality"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableXTLS()">
|
||||
<a-form-item v-if="inbound.canEnableXtls()">
|
||||
<span slot="label">
|
||||
XTLS
|
||||
<a-tooltip>
|
||||
@ -27,14 +27,14 @@
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.XTLS"></a-switch>
|
||||
<a-switch v-model="inbound.xtls"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tls settings -->
|
||||
<a-form v-if="inbound.tls || inbound.XTLS" layout="inline">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication" v-if="inbound.tls">
|
||||
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input>
|
||||
<a-form v-if="inbound.tls" layout="inline">
|
||||
<a-form-item label='{{ i18n "domainName" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="CipherSuites">
|
||||
<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>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS" v-if="inbound.tls" >
|
||||
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<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 v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</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-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-group>
|
||||
</a-form-item>
|
||||
<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 label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
||||
@ -93,33 +93,79 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
</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-item label="show">
|
||||
<a-form-item label="Show">
|
||||
<a-switch v-model="inbound.stream.reality.show">
|
||||
</a-switch>
|
||||
</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-form-item>
|
||||
<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>
|
||||
</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 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 label="serverNames">
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 360px"></a-input>
|
||||
<a-form-item label="Server Names">
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="privateKey">
|
||||
<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-form-item label="ShortIds">
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
||||
</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>
|
||||
{{end}}
|
@ -49,10 +49,14 @@
|
||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-if="inbound.XTLS">
|
||||
<td v-else-if="inbound.xtls">
|
||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</td>
|
||||
<td v-else-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>
|
||||
</tr>
|
||||
|
@ -43,6 +43,14 @@
|
||||
loading(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 = {
|
||||
@ -62,6 +70,7 @@
|
||||
inModal: inModal,
|
||||
Protocols: protocols,
|
||||
SSMethods: SSMethods,
|
||||
delayedStart: false,
|
||||
get inbound() {
|
||||
return inModal.inbound;
|
||||
},
|
||||
@ -70,36 +79,40 @@
|
||||
},
|
||||
get 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: {
|
||||
streamNetworkChange(oldValue) {
|
||||
if (oldValue === 'kcp') {
|
||||
this.inModal.inbound.tls = false;
|
||||
streamNetworkChange() {
|
||||
if (!inModal.inbound.canSetTls()) {
|
||||
this.inModal.inbound.stream.security = 'none';
|
||||
}
|
||||
},
|
||||
addClient(protocol, clients) {
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||
default: return null;
|
||||
if (!inModal.inbound.canEnableReality()) {
|
||||
this.inModal.inbound.reality = false;
|
||||
}
|
||||
},
|
||||
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(){
|
||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
||||
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) {
|
||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var string = '';
|
||||
|
@ -133,26 +133,26 @@
|
||||
<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;" 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>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
<template v-if="clientCount[dbInbound.id]">
|
||||
<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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<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 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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</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-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">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
@ -531,9 +531,9 @@
|
||||
title: '{{ i18n "pages.client.add"}}',
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: false
|
||||
@ -545,9 +545,9 @@
|
||||
title: '{{ i18n "pages.client.bulk"}} ' + dbInbound.remark,
|
||||
okText: '{{ i18n "pages.client.bulk"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound) => {
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientsBulkModal.loading();
|
||||
await this.addClient(inbound, dbInbound);
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientsBulkModal.close();
|
||||
},
|
||||
});
|
||||
@ -561,9 +561,9 @@
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (inbound, dbInbound, index) => {
|
||||
confirm: async (client, dbInboundId, index) => {
|
||||
clientModal.loading();
|
||||
await this.updateClient(inbound, dbInbound, index);
|
||||
await this.updateClient(client, dbInboundId, index);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
@ -573,17 +573,17 @@
|
||||
firstKey = Object.keys(client)[0];
|
||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
||||
},
|
||||
async addClient(inbound, dbInbound) {
|
||||
async addClient(clients, dbInboundId) {
|
||||
const data = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
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 = {
|
||||
id: dbInbound.id,
|
||||
settings: inbound.settings.toString(),
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() +']}',
|
||||
};
|
||||
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.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.xrayConfigIRWARP"}}' desc='{{ i18n "pages.setting.xrayConfigIRWARPDesc"}}' v-model="IRWARPSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</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
|
||||
}
|
||||
|
||||
func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) {
|
||||
func (s *InboundService) getAllEmails() ([]string, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan})
|
||||
if ignoreId > 0 {
|
||||
db = db.Where("id != ?", ignoreId)
|
||||
}
|
||||
db = db.Find(&inbounds)
|
||||
if db.Error != nil {
|
||||
return "", db.Error
|
||||
}
|
||||
var emails []string
|
||||
err := db.Raw(`
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
`).Scan(&emails).Error
|
||||
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.getClients(inbound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
if emails[client.Email] {
|
||||
func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
|
||||
allEmails, err := s.getAllEmails()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var emails []string
|
||||
for _, client := range clients {
|
||||
if client.Email != "" {
|
||||
if s.contains(emails, client.Email) {
|
||||
return client.Email, nil
|
||||
}
|
||||
if s.contains(allEmails, client.Email) {
|
||||
return client.Email, nil
|
||||
}
|
||||
emails = append(emails, client.Email)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
emails := make(map[string]bool)
|
||||
allEmails, err := s.getAllEmails()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var emails []string
|
||||
for _, client := range clients {
|
||||
if client.Email != "" {
|
||||
if emails[client.Email] {
|
||||
if s.contains(emails, client.Email) {
|
||||
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) {
|
||||
@ -215,14 +239,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
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)
|
||||
if err != nil {
|
||||
return inbound, err
|
||||
@ -245,8 +261,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
func (s *InboundService) AddInboundClient(data *model.Inbound) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -255,29 +275,35 @@ func (s *InboundService) AddInboundClient(inbound *model.Inbound) error {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
var settings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = inbound.Settings
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
if len(clients[len(clients)-1].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[len(clients)-1])
|
||||
}
|
||||
for i := len(oldClients); i < len(clients); i++ {
|
||||
if len(clients[i].Email) > 0 {
|
||||
s.AddClientStat(inbound.Id, &clients[i])
|
||||
for _, client := range clients {
|
||||
if len(client.Email) > 0 {
|
||||
s.AddClientStat(data.Id, &client)
|
||||
}
|
||||
}
|
||||
db := database.GetDB()
|
||||
@ -309,21 +335,13 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string)
|
||||
return db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error {
|
||||
existEmail, err := s.checkEmailExistForInbound(inbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existEmail != "" {
|
||||
return common.NewError("Duplicate email:", existEmail)
|
||||
}
|
||||
|
||||
clients, err := s.getClients(inbound)
|
||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound, err := s.GetInbound(inbound.Id)
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -333,13 +351,40 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||
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()
|
||||
|
||||
if len(clients[index].Email) > 0 {
|
||||
if len(clients[0].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 {
|
||||
return err
|
||||
}
|
||||
@ -348,7 +393,7 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
s.AddClientStat(inbound.Id, &clients[index])
|
||||
s.AddClientStat(data.Id, &clients[0])
|
||||
}
|
||||
} else {
|
||||
err = s.DelClientStat(db, oldClients[index].Email)
|
||||
@ -507,6 +552,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||
count := result.RowsAffected
|
||||
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() {
|
||||
db := database.GetDB()
|
||||
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 {
|
||||
db := database.GetDB()
|
||||
|
||||
|
@ -390,3 +390,29 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
||||
|
||||
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/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"gorm.io/gorm"
|
||||
@ -18,12 +19,15 @@ type SubService struct {
|
||||
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
|
||||
var result []string
|
||||
var header string
|
||||
var traffic xray.ClientTraffic
|
||||
var clientTraffics []xray.ClientTraffic
|
||||
inbounds, err := s.getInboundsBySubId(subId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.getClients(inbound)
|
||||
@ -37,22 +41,60 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) {
|
||||
if client.SubID == subId {
|
||||
link := s.getLink(inbound, client.Email)
|
||||
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) {
|
||||
db := database.GetDB()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
switch inbound.Protocol {
|
||||
case "vmess":
|
||||
@ -296,7 +338,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
@ -306,15 +348,15 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
params["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
|
||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
if xtlsSetting != nil {
|
||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
||||
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||
params["sni"], _ = sniValue.(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||
params["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||
if insecure.(bool) {
|
||||
params["allowInsecure"] = "1"
|
||||
}
|
||||
@ -465,7 +507,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
|
||||
if security == "xtls" {
|
||||
params["security"] = "xtls"
|
||||
xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{})
|
||||
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
|
||||
alpns, _ := xtlsSetting["alpn"].([]interface{})
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
@ -475,15 +517,15 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
params["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
|
||||
XTLSSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||
if xtlsSetting != nil {
|
||||
if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok {
|
||||
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
||||
params["sni"], _ = sniValue.(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok {
|
||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||
params["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok {
|
||||
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
|
||||
if insecure.(bool) {
|
||||
params["allowInsecure"] = "1"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user