Merge pull request #227 from alireza0:Reality

Reality
This commit is contained in:
Alireza Ahmadi
2023-04-18 09:32:12 +02:00
committed by GitHub
35 changed files with 1705 additions and 813 deletions

View File

@@ -26,7 +26,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-64.zip wget https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip geoip.dat geosite.dat rm -f Xray-linux-64.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
@@ -66,7 +66,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
@@ -106,7 +106,7 @@ jobs:
mv xui-release x-ui mv xui-release x-ui
mkdir bin mkdir bin
cd bin cd bin
wget https://github.com/xtls/xray-core/releases/latest/download/Xray-linux-s390x.zip wget https://github.com/xtls/xray-core/releases/download/v1.8.1/Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat rm -f Xray-linux-s390x.zip geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat

View File

@@ -11,6 +11,12 @@ ENV TZ=Asia/Tehran
WORKDIR /app WORKDIR /app
RUN apk add ca-certificates tzdata && mkdir bin RUN apk add ca-certificates tzdata && mkdir bin
# Download latest rule files
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat \
https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat \
bin/
COPY --from=builder /app/main /app/x-ui COPY --from=builder /app/main /app/x-ui
VOLUME [ "/etc/x-ui" ] VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ] CMD [ "./x-ui" ]

View File

@@ -88,11 +88,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
``` ```
## Install custom version ## Install custom version
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
To install your desired version you can add the version to the end of install command. Example for ver `0.5.1`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.1 bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
``` ```
## Manual install & upgrade ## Manual install & upgrade
@@ -147,10 +145,6 @@ docker build -t x-ui .
<details> <details>
<summary>Click for details</summary> <summary>Click for details</summary>
### Cloudflare
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
### Certbot ### Certbot
```bash ```bash
@@ -168,8 +162,6 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
<details> <details>
<summary>Click for details</summary> <summary>Click for details</summary>
> This feature and tutorial are provided by [FranzKafkaYu](https://github.com/FranzKafkaYu)
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html) X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
Set the robot-related parameters in the panel background, including: Set the robot-related parameters in the panel background, including:
@@ -184,7 +176,7 @@ Set the robot-related parameters in the panel background, including:
Reference syntax: Reference syntax:
- 30 \* \* \* \* \* //Notify at the 30s of each point - 30 \* \* \* \* \* //Notify at the 30s of each point
- 0 _/10 _ \* \* \* //Notify at the first second of each 10 minutes - 0 */10 \* \* \* \* //Notify at the first second of each 10 minutes
- @hourly // hourly notification - @hourly // hourly notification
- @daily // Daily notification (00:00 in the morning) - @daily // Daily notification (00:00 in the morning)
- @every 8h // notify every 8 hours - @every 8h // notify every 8 hours

View File

@@ -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;

View File

@@ -43,13 +43,9 @@ const RULE_DOMAIN = {
SPEEDTEST: 'geosite:speedtest', SPEEDTEST: 'geosite:speedtest',
}; };
const XTLS_FLOW_CONTROL = {
ORIGIN: "xtls-rprx-origin",
DIRECT: "xtls-rprx-direct",
};
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 = {
@@ -103,7 +99,6 @@ Object.freeze(VmessMethods);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(RULE_IP); Object.freeze(RULE_IP);
Object.freeze(RULE_DOMAIN); Object.freeze(RULE_DOMAIN);
Object.freeze(XTLS_FLOW_CONTROL);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
Object.freeze(TLS_VERSION_OPTION); Object.freeze(TLS_VERSION_OPTION);
Object.freeze(TLS_CIPHER_OPTION); Object.freeze(TLS_CIPHER_OPTION);
@@ -479,7 +474,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '', cipherSuites = '',
certificates=[new TlsStreamSettings.Cert()], certificates=[new TlsStreamSettings.Cert()],
alpn=[], alpn=[],
settings=[new TlsStreamSettings.Settings()]) { settings=new TlsStreamSettings.Settings()) {
super(); super();
this.server = serverName; this.server = serverName;
this.minVersion = minVersion; this.minVersion = minVersion;
@@ -506,8 +501,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,
@@ -528,7 +522,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,
}; };
} }
} }
@@ -597,10 +591,92 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
} }
}; };
class RealityStreamSettings extends XrayCommonClass {
constructor(show = false, xver = 0,
dest = 'microsoft.com:443',
serverNames = 'microsoft.com,www.microsoft.com',
privateKey = '', minClient = '', maxClient = '',
maxTimediff = 0, shortIds = [],
settings= new RealityStreamSettings.Settings()) {
super();
this.show = show;
this.xver = xver;
this.dest = dest;
this.serverNames = serverNames instanceof Array ? serverNames.join(",") : serverNames;
this.privateKey = 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, json.settings.spiderX);
}
return new RealityStreamSettings(
json.show,
json.xver,
json.dest,
json.serverNames,
json.privateKey,
json.minClient,
json.maxClient,
json.maxTimediff,
json.shortIds,
json.settings,
);
}
toJson() {
return {
show: this.show,
xver: this.xver,
dest: this.dest,
serverNames: this.serverNames.split(","),
privateKey: this.privateKey,
minClient: this.minClient,
maxClient: this.maxClient,
maxTimediff: this.maxTimediff,
shortIds: this.shortIds.split(","),
settings: this.settings,
};
}
}
RealityStreamSettings.Settings = class extends XrayCommonClass {
constructor(publicKey = '', fingerprint = '', serverName = '', spiderX= '/') {
super();
this.publicKey = publicKey;
this.fingerprint = fingerprint;
this.serverName = serverName;
this.spiderX = spiderX;
}
static fromJson(json = {}) {
return new RealityStreamSettings.Settings(
json.publicKey,
json.fingerprint,
json.serverName,
json.spiderX,
);
}
toJson() {
return {
publicKey: this.publicKey,
fingerprint: this.fingerprint,
serverName: this.serverName,
spiderX: this.spiderX,
};
}
};
class StreamSettings extends XrayCommonClass { class StreamSettings extends XrayCommonClass {
constructor(network='tcp', constructor(network='tcp',
security='none', security='none',
tlsSettings=new TlsStreamSettings(), tlsSettings=new TlsStreamSettings(),
realitySettings = new RealityStreamSettings(),
tcpSettings=new TcpStreamSettings(), tcpSettings=new TcpStreamSettings(),
kcpSettings=new KcpStreamSettings(), kcpSettings=new KcpStreamSettings(),
wsSettings=new WsStreamSettings(), wsSettings=new WsStreamSettings(),
@@ -612,6 +688,7 @@ class StreamSettings extends XrayCommonClass {
this.network = network; this.network = network;
this.security = security; this.security = security;
this.tls = tlsSettings; this.tls = tlsSettings;
this.reality = realitySettings;
this.tcp = tcpSettings; this.tcp = tcpSettings;
this.kcp = kcpSettings; this.kcp = kcpSettings;
this.ws = wsSettings; this.ws = wsSettings;
@@ -632,29 +709,25 @@ class StreamSettings extends XrayCommonClass {
} }
} }
get isXTls() { get isReality() {
return this.security === "xtls"; return this.security === "reality";
} }
set isXTls(isXTls) { set isReality(isReality) {
if (isXTls) { if (isReality) {
this.security = 'xtls'; this.security = 'reality';
} else { } else {
this.security = 'none'; this.security = 'none';
} }
} }
static fromJson(json={}) { static fromJson(json={}) {
let tls;
if (json.security === "xtls") {
tls = TlsStreamSettings.fromJson(json.xtlsSettings);
} else {
tls = TlsStreamSettings.fromJson(json.tlsSettings);
}
return new StreamSettings( return new StreamSettings(
json.network, json.network,
json.security, json.security,
tls, TlsStreamSettings.fromJson(json.tlsSettings),
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),
@@ -670,7 +743,7 @@ 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, realitySettings: this.isReality ? this.reality.toJson() : undefined,
tcpSettings: network === 'tcp' ? this.tcp.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,
@@ -750,13 +823,13 @@ class Inbound extends XrayCommonClass {
} }
} }
get xtls() { get reality() {
return this.stream.security === 'xtls'; return this.stream.security === 'reality';
} }
set xtls(isXTls) { set reality(isReality) {
if (isXTls) { if (isReality) {
this.stream.security = 'xtls'; this.stream.security = 'reality';
} else { } else {
this.stream.security = 'none'; this.stream.security = 'none';
} }
@@ -865,7 +938,7 @@ class Inbound extends XrayCommonClass {
} }
get serverName() { get serverName() {
if (this.stream.isTls || this.stream.isXTls) { if (this.stream.isTls || this.stream.isReality) {
return this.stream.tls.server; return this.stream.tls.server;
} }
return ""; return "";
@@ -961,7 +1034,7 @@ class Inbound extends XrayCommonClass {
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
canEnableTlsFlow() { canEnableTlsFlow() {
if ((this.stream.security === 'tls') && (this.network === "tcp")) { if ((this.stream.security != 'none') && (this.network === "tcp")) {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
return true; return true;
@@ -976,7 +1049,7 @@ class Inbound extends XrayCommonClass {
return this.canEnableTls(); return this.canEnableTls();
} }
canEnableXTls() { canEnableReality() {
switch (this.protocol) { switch (this.protocol) {
case Protocols.VLESS: case Protocols.VLESS:
case Protocols.TROJAN: case Protocols.TROJAN:
@@ -984,7 +1057,15 @@ class Inbound extends XrayCommonClass {
default: default:
return false; return false;
} }
return this.network === "tcp";
switch (this.network) {
case "tcp":
case "http":
case "grpc":
return true;
default:
return false;
}
} }
canEnableStream() { canEnableStream() {
@@ -1081,10 +1162,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));
} }
@@ -1143,32 +1224,43 @@ 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.reality) {
params.set("security", "xtls"); params.set("security", "reality");
params.set("alpn", this.stream.tls.alpn); params.set("pbk", this.stream.reality.settings.publicKey);
if(this.stream.tls.settings[0].allowInsecure){ if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("allowInsecure", "1"); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) { if (this.stream.reality.shortIds != "") {
address = this.stream.tls.server; params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (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 (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX);
}
if (this.stream.network === 'tcp') {
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
const link = `vless://${uuid}@${address}:${port}`; const link = `vless://${uuid}@${address}:${port}`;
@@ -1247,29 +1339,40 @@ 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.xtls) { if (this.reality) {
params.set("security", "xtls"); params.set("security", "reality");
params.set("alpn", this.stream.tls.alpn); params.set("pbk", this.stream.reality.settings.publicKey);
if(this.stream.tls.settings[0].allowInsecure){ if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
params.set("allowInsecure", "1"); params.set("sni", this.stream.reality.serverNames.split(",")[0]);
}
if (this.stream.reality.shortIds != "") {
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
}
if (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 (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
params.set("spx", this.stream.reality.settings.spiderX);
}
if (this.stream.network === 'tcp') {
params.set("flow", this.settings.vlesses[clientIndex].flow);
} }
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
address = this.stream.tls.server;
}
params.set("flow", this.settings.trojans[clientIndex].flow);
} }
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`; const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`;

View File

@@ -31,7 +31,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/add", a.addInbound) g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound) g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound) g.POST("/update/:id", a.updateInbound)
g.POST("/addClient/", a.addInboundClient) g.POST("/addClient", a.addInboundClient)
g.POST("/delClient/:email", a.delInboundClient) g.POST("/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)
@@ -129,19 +129,19 @@ func (a *InboundController) updateInbound(c *gin.Context) {
} }
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()
} }

View File

@@ -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)
}

View File

@@ -4,73 +4,140 @@
:class="siderDrawer.isDarkTheme ? darkClass : ''" :class="siderDrawer.isDarkTheme ? darkClass : ''"
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'> :ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.client.method" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option :value="0">Random</a-select-option> <td>{{ i18n "pages.client.method" }}</td>
<a-select-option :value="1">Random+Prefix</a-select-option> <td>
<a-select-option :value="2">Random+Prefix+Num</a-select-option> <a-form-item>
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option> <a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px"
<a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option> :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-select> <a-select-option :value="0">Random</a-select-option>
</a-form-item><br /> <a-select-option :value="1">Random+Prefix</a-select-option>
<a-form-item v-if="clientsBulkModal.emailMethod>1"> <a-select-option :value="2">Random+Prefix+Num</a-select-option>
<span slot="label">{{ i18n "pages.client.first" }}</span> <a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number> <a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
</a-form-item> </a-select>
<a-form-item v-if="clientsBulkModal.emailMethod>1"> </a-form-item>
<span slot="label">{{ i18n "pages.client.last" }}</span> </td>
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number> </tr>
</a-form-item> <tr v-if="clientsBulkModal.emailMethod>1">
<a-form-item v-if="clientsBulkModal.emailMethod>0"> <td>{{ i18n "pages.client.first" }}</td>
<span slot="label">{{ i18n "pages.client.prefix" }}</span> <td>
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
<a-form-item v-if="clientsBulkModal.emailMethod>2"> </a-form-item>
<span slot="label">{{ i18n "pages.client.postfix" }}</span> </td>
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input> </tr>
</a-form-item> <tr v-if="clientsBulkModal.emailMethod>1">
<a-form-item v-if="clientsBulkModal.emailMethod < 2"> <td>{{ i18n "pages.client.last" }}</td>
<span slot="label">{{ i18n "pages.client.clientCount" }}</span> <td>
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
<a-form-item label="Subscription"> </a-form-item>
<a-input v-model.trim="clientsBulkModal.subId"></a-input> </td>
</a-form-item> </tr>
<a-form-item label="Telegram ID"> <tr v-if="clientsBulkModal.emailMethod>0">
<a-input v-model.trim="clientsBulkModal.tgId"></a-input> <td>{{ i18n "pages.client.prefix" }}</td>
</a-form-item> <td>
<a-form-item> <a-form-item>
<span slot="label"> <a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <tr v-if="clientsBulkModal.emailMethod>2">
</template> <td>{{ i18n "pages.client.postfix" }}</td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> </tr>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch> <tr v-if="clientsBulkModal.emailMethod < 2">
</a-form-item> <td>{{ i18n "pages.client.clientCount" }}</td>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientsBulkModal.delayedStart"> <td>
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <a-form-item>
</a-form-item> <a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
<a-form-item v-else> </a-form-item>
<span slot="label"> </td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> </tr>
<a-tooltip> <tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
<template slot="title"> <td>Flow</td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <td>
</template> <a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> <a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-tooltip> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
</span> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </a-select>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" </a-form-item>
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker> </td>
</a-form-item> </tr>
<tr>
<td>Subscription</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Telegram Username</td>
<td>
<a-form-item>
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientsBulkModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
@@ -83,7 +150,6 @@
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,
expiryTime: '', expiryTime: '',
@@ -94,8 +160,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;
@@ -115,9 +183,12 @@
newClient.tgId = clientsBulkModal.tgId; newClient.tgId = clientsBulkModal.tgId;
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;
}
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)=>{} }) { show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
this.visible = true; this.visible = true;
@@ -134,9 +205,9 @@
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) {

View File

@@ -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: [],
@@ -20,9 +21,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;

View File

@@ -9,7 +9,7 @@
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input> <a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
</template> </template>
<template v-else-if="type === 'number'"> <template v-else-if="type === 'number'">
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input> <a-input-number :value="value" @input="$emit('input', $event.target.value)"></a-input-number>
</template> </template>
<template v-else-if="type === 'textarea'"> <template v-else-if="type === 'textarea'">
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea> <a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>

View File

@@ -3,88 +3,143 @@
<template v-if="isEdit"> <template v-if="isEdit">
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag> <a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
</template> </template>
<a-form-item> <table width="100%" class="ant-table-tbody">
<span slot="label"> <tr>
<span>{{ i18n "pages.inbounds.Email" }}</span> <td>{{ i18n "pages.inbounds.enable" }}</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item>
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span> <a-switch v-model="client.enable"></a-switch>
</template> </a-form-item>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-form-item label="{{ i18n "pages.inbounds.enable" }}"> <a-tooltip>
<a-switch v-model="client.enable"></a-switch> <template slot="title">
</a-form-item> <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
<a-form-item label="password" v-if="inbound.protocol === Protocols.TROJAN"> </template>
<a-input v-model.trim="client.password"></a-input> <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
</a-form-item> </a-tooltip>
<a-form-item label="id" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS"> </td>
<a-input v-model.trim="client.id"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS"> <a-input v-model.trim="client.email" style="width: 250px"></a-input>
<a-input type="number" v-model.number="client.alterId"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label="Subscription" v-if="client.email"> </tr>
<a-input v-model.trim="client.subId"></a-input> <tr v-if="inbound.protocol === Protocols.TROJAN">
</a-form-item> <td>password</td>
<a-form-item label="Telegram Username" v-if="client.email"> <td>
<a-input v-model.trim="client.tgId"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="client.password" style="width: 250px"></a-input>
<a-form-item v-if="inbound.xtls" label="flow"> </a-form-item>
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="">{{ i18n "none" }}</a-select-option> </tr>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
</a-select> <td>ID</td>
</a-form-item> <td>
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline"> <a-form-item>
<a-select v-model="client.flow" style="width: 150px"> <a-input v-model.trim="client.id" style="width: 250px"></a-input>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> </a-form-item>
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> </td>
</a-select> </tr>
</a-form-item> <tr v-if="inbound.protocol === Protocols.VMESS">
<a-form-item> <td>{{ i18n "additional" }}</td>
<span slot="label"> <td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <a-form-item>
<a-tooltip> <a-input-number v-model.number="client.alterId"></a-input-number>
<template slot="title"> </a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> </td>
</template> </tr>
<a-icon type="question-circle" theme="filled"></a-icon> <tr v-if="client.email">
</a-tooltip> <td>Subscription</td>
</span> <td>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <a-form-item>
<template v-if="isEdit && clientStats"> <a-input v-model.trim="client.subId" style="width: 250px"></a-input>
<span>{{ i18n "usage" }}:</span> </a-form-item>
<a-tag :color="statsColor"> </td>
[[ sizeFormat(clientStats.up) ]] / </tr>
[[ sizeFormat(clientStats.down) ]] <tr v-if="client.email">
([[ sizeFormat(clientStats.up + clientStats.down) ]]) <td>Telegram Username</td>
</a-tag> <td>
</template> <a-form-item>
</a-form-item> <a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
<a-form-item label="{{ i18n "pages.client.delayedStart" }}"> </a-form-item>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch> </td>
</a-form-item> </tr>
<a-form-item label="{{ i18n "pages.client.expireDays" }}" v-if="clientModal.delayedStart"> <tr v-if="inbound.canEnableTlsFlow()">
<a-input type="number" v-model.number="delayedExpireDays" :min="0"></a-input> <td>Flow</td>
</a-form-item> <td>
<a-form-item v-else> <a-form-item>
<span slot="label"> <a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<a-tooltip> <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<template slot="title"> </a-select>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </a-form-item>
</template> </td>
<a-icon type="question-circle" theme="filled"></a-icon> </tr>
</a-tooltip> <tr>
</span> <td>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <a-tooltip>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <template slot="title">
<a-tag color="red" v-if="isExpiry">Expired</a-tag> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</a-form-item> </template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-if="isEdit && clientStats">
<td>{{ i18n "usage" }}</td>
<td>
<a-tag :color="statsColor">
[[ sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="clientModal.delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,58 +1,91 @@
{{define "form/inbound"}} {{define "form/inbound"}}
<!-- base --> <!-- base -->
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "remark" }}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="dbInbound.remark"></a-input> <tr>
</a-form-item> <td>{{ i18n "enable" }}</td>
<a-form-item label='{{ i18n "enable" }}'> <td>
<a-switch v-model="dbInbound.enable"></a-switch> <a-form-item>
</a-form-item> <a-switch v-model="dbInbound.enable"></a-switch>
<a-form-item label='{{ i18n "protocol" }}'> </a-form-item>
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option> </tr>
</a-select> <tr>
</a-form-item> <td>{{ i18n "remark" }}</td>
<a-form-item> <td>
<span slot="label"> <a-form-item>
{{ i18n "monitor" }} <a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
<a-tooltip> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "protocol" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "monitor" }}
<a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span> <span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
</template> </template>
<a-icon type="question-circle" theme="filled"></a-icon> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="inbound.listen"></a-input> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
<a-input type="number" v-model.number="inbound.port"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<span slot="label"> <tr>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <td>{{ i18n "pages.inbounds.port" }}</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <a-input-number v-model.number="inbound.port"></a-input-number>
</template> </a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> <td>
</a-form-item> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-form-item> <a-tooltip>
<span slot="label"> <template slot="title">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
<a-tooltip> </template>
<template slot="title"> <a-icon type="question-circle" theme="filled"></a-icon>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </a-tooltip>
</template> </td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </a-form-item>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" </td>
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker> </tr>
</a-form-item> <tr>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<!-- vmess settings --> <!-- vmess settings -->

View File

@@ -1,20 +1,42 @@
{{define "form/dokodemo"}} {{define "form/dokodemo"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.settings.address"></a-input> <tr>
</a-form-item> <td>{{ i18n "pages.inbounds.targetAddress"}}</td>
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'> <td>
<a-input type="number" v-model.number="inbound.settings.port"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.address"></a-input>
<a-form-item label='{{ i18n "pages.inbounds.network"}}'> </a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="tcp,udp">tcp+udp</a-select-option> </tr>
<a-select-option value="tcp">tcp</a-select-option> <tr>
<a-select-option value="udp">udp</a-select-option> <td>{{ i18n "pages.inbounds.destinationPort"}}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label="FollowRedirect"> <a-input-number v-model.number="inbound.settings.port"></a-input-number>
<a-switch v-model="inbound.settings.followRedirect"></a-switch> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network"}}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
<tr>
<td>FollowRedirect</td>
<td>
<a-form-item>
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,10 +1,22 @@
{{define "form/http"}} {{define "form/http"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "username"}}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input> <tr>
</a-form-item> <td>{{ i18n "username"}}</td>
<a-form-item label='{{ i18n "password" }}'> <td>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input> <a-form-item>
</a-form-item> <a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,19 +1,36 @@
{{define "form/shadowsocks"}} {{define "form/shadowsocks"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "encryption" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> <td>{{ i18n "encryption" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-select v-model="inbound.settings.method" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-input v-model.trim="inbound.settings.password"></a-input> <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "pages.inbounds.network" }}'> </a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="tcp,udp">tcp+udp</a-select-option> </tr>
<a-select-option value="tcp">tcp</a-select-option> <tr>
<a-select-option value="udp">udp</a-select-option> <td>{{ i18n "password" }}</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-input v-model.trim="inbound.settings.password"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
<a-select-option value="tcp">tcp</a-select-option>
<a-select-option value="udp">udp</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,24 +1,50 @@
{{define "form/socks"}} {{define "form/socks"}}
<a-form layout="inline"> <a-form layout="inline">
<!-- <a-form-item label="Password authentication">--> <!-- <a-form-item label="Password authentication">-->
<a-form-item label='{{ i18n "password" }}'> <table width="100%" class="ant-table-tbody">
<a-switch :checked="inbound.settings.auth === 'password'" <tr>
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch> <td>{{ i18n "password" }}</td>
</a-form-item> <td>
<a-form-item>
<a-switch :checked="inbound.settings.auth === 'password'"
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
</a-form-item>
</td>
</tr>
<template v-if="inbound.settings.auth === 'password'"> <template v-if="inbound.settings.auth === 'password'">
<a-form-item label='{{ i18n "username" }}'> <tr>
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input> <td>{{ i18n "username" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "password" }}'> <a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input> <a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "password" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'> <tr>
<a-switch v-model="inbound.settings.udp"></a-switch> <td>{{ i18n "pages.inbounds.enable" }} udp</td>
</a-form-item> <td>
<a-form-item v-if="inbound.settings.udp" <a-form-item>
label="IP"> <a-switch v-model="inbound.settings.udp"></a-switch>
<a-input v-model.trim="inbound.settings.ip"></a-input> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>IP</td>
<td>
<a-form-item v-if="inbound.settings.udp">
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -2,9 +2,9 @@
<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"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@@ -12,45 +12,88 @@
</template> </template>
<a-icon @click="getNewEmail(client)" type="sync"> </a-icon> <a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="password"> </a-form-item>
<a-input v-model.trim="client.password"></a-input> </td>
</a-form-item> </tr>
<a-form-item v-if="inbound.xtls" label="flow"> <tr>
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>password</td>
<a-select-option value="">{{ i18n "none" }}</a-select-option> <td>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-form-item>
</a-select> <a-input v-model.trim="client.password" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item> </td>
<span slot="label"> </tr>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <tr>
<a-tooltip> <td>Subscription</td>
<template slot="title"> <td>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <a-form-item v-if="client.email">
</template> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
<a-icon type="question-circle" theme="filled"></a-icon> </a-form-item>
</a-tooltip> </td>
</span> </tr>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <tr>
</a-form-item> <td>Telegram Username</td>
<a-form-item> <td>
<span slot="label"> <a-form-item v-if="client.email">
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
<a-tooltip> </a-form-item>
<template slot="title"> </td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> </tr>
</template> <tr>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
</span> <a-tooltip>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <template slot="title">
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> </template>
</a-form-item> <a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -65,7 +108,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)"> <template v-if="inbound.isTcp && inbound.tls">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>
@@ -97,7 +140,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="xver"> <a-form-item label="xver">
<a-input type="number" v-model.number="fallback.xver"></a-input> <a-input-number v-model.number="fallback.xver"></a-input-number>
</a-form-item> </a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/> <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form> </a-form>

View File

@@ -2,61 +2,109 @@
<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"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.inbounds.EmailDesc" }}</span> <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
</template> </template>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> <a-icon @click="getNewEmail(client)" type="sync"> </a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="id"> </a-form-item>
<a-input v-model.trim="client.id"></a-input> </td>
</a-form-item> </tr>
<a-form-item v-if="inbound.xtls" label="flow"> <tr>
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>id</td>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <td>
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <a-form-item>
</a-select> <a-input v-model.trim="client.id" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="flow" layout="inline"> </td>
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 150px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </tr>
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option> <tr v-if="inbound.canEnableTlsFlow()">
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option> <td>flow</td>
</a-select> <td>
</a-form-item> <a-form-item>
<a-form-item> <a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<span slot="label"> <a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) <a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
<a-tooltip> </a-select>
<template slot="title"> </a-form-item>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> </td>
</template> </tr>
<a-icon type="question-circle" theme="filled"></a-icon> <tr>
</a-tooltip> <td>Subscription</td>
</span> <td>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> <a-form-item v-if="client.email">
</a-form-item> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
<a-form-item> </a-form-item>
<span slot="label"> </td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> </tr>
<a-tooltip> <tr>
<template slot="title"> <td>Telegram Username</td>
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <td>
</template> <a-form-item v-if="client.email">
<a-icon type="question-circle" theme="filled"></a-icon> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-tooltip> </a-form-item>
</span> </td>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" </tr>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <tr>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <td>
</a-form-item> <span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -71,7 +119,7 @@
</table> </table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<template v-if="inbound.isTcp && (inbound.tls || inbound.xtls)"> <template v-if="inbound.isTcp && inbound.tls">
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="Fallbacks"> <a-form-item label="Fallbacks">
<a-row> <a-row>
@@ -103,7 +151,7 @@
<a-input v-model="fallback.dest"></a-input> <a-input v-model="fallback.dest"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="xver"> <a-form-item label="xver">
<a-input type="number" v-model.number="fallback.xver"></a-input> <a-input-number v-model.number="fallback.xver"></a-input-number>
</a-form-item> </a-form-item>
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/> <a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
</a-form> </a-form>

View File

@@ -2,9 +2,9 @@
<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"> <table width="100%" class="ant-table-tbody">
<a-form-item> <tr>
<span slot="label"> <td>
<span>{{ i18n "pages.inbounds.Email" }}</span> <span>{{ i18n "pages.inbounds.Email" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
@@ -12,42 +12,96 @@
</template> </template>
<a-icon type="sync" @click="getNewEmail(client)"></a-icon> <a-icon type="sync" @click="getNewEmail(client)"></a-icon>
</a-tooltip> </a-tooltip>
</span> </td>
<a-input v-model.trim="client.email"></a-input> <td>
</a-form-item> <a-form-item>
</a-form> <a-input v-model.trim="client.email" style="width: 200px;"></a-input>
<a-form-item label="id"> </a-form-item>
<a-input v-model.trim="client.id"></a-input> </td>
</a-form-item> </tr>
<a-form-item label='{{ i18n "additional" }} ID'> <tr>
<a-input type="number" v-model.number="client.alterId"></a-input> <td>id</td>
</a-form-item> <td>
<a-form-item> <a-form-item>
<span slot="label"> <a-input v-model.trim="client.id" style="width: 200px;"></a-input>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB) </a-form-item>
<a-tooltip> </td>
<template slot="title"> </tr>
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> <tr>
</template> <td>{{ i18n "additional" }}</td>
<a-icon type="question-circle" theme="filled"></a-icon> <td>
</a-tooltip> <a-form-item>
</span> <a-input-number v-model.number="client.alterId"></a-input-number>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> </a-form-item>
</a-form-item> </td>
<a-form-item> </tr>
<span slot="label"> <tr>
<span >{{ i18n "pages.inbounds.expireDate" }}</span> <td>Subscription</td>
<a-tooltip> <td>
<template slot="title"> <a-form-item v-if="client.email">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> <a-input v-model.trim="client.subId" style="width: 200px;"></a-input>
</template> </a-form-item>
<a-icon type="question-circle" theme="filled"></a-icon> </td>
</a-tooltip> </tr>
</span> <tr>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm" <td>Telegram Username</td>
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" <td>
v-model="client._expiryTime" style="width: 300px;"></a-date-picker> <a-form-item v-if="client.email">
</a-form-item> <a-input v-model.trim="client.tgId" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>
<span >{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
<a-tooltip>
<template slot="title">
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.client.delayedStart" }}</td>
<td>
<a-form-item>
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
</a-form-item>
</td>
</tr>
<tr v-if="delayedStart">
<td>{{ i18n "pages.client.expireDays" }}</td>
<td>
<a-form-item>
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr v-else>
<td>
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</td>
<td>
<a-form-item>
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
</a-form-item>
</td>
</tr>
</table>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-collapse v-else> <a-collapse v-else>
@@ -64,8 +118,7 @@
</a-collapse> </a-collapse>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'> <a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch> <a-switch v-model="inbound.settings.disableInsecure"></a-switch>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,7 +1,14 @@
{{define "form/streamGRPC"}} {{define "form/streamGRPC"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="serviceName"> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input> <tr>
</a-form-item> <td>serviceName</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,12 +1,24 @@
{{define "form/streamHTTP"}} {{define "form/streamHTTP"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "path" }}'> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.http.path"></a-input> <tr>
</a-form-item> <td>{{ i18n "path" }}</td>
<a-form-item label="host"> <td>
<a-row v-for="(host, index) in inbound.stream.http.host"> <a-form-item>
<a-input v-model.trim="inbound.stream.http.host[index]"></a-input> <a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
</a-row> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>host</td>
<td>
<a-form-item>
<a-row v-for="(host, index) in inbound.stream.http.host">
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
</a-row>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,38 +1,85 @@
{{define "form/streamKCP"}} {{define "form/streamKCP"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "camouflage" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="none">nonenot camouflage</a-select-option> <td>{{ i18n "camouflage" }}</td>
<a-select-option value="srtp">srtpcamouflage video call</a-select-option> <td>
<a-select-option value="utp">utpcamouflage BT download</a-select-option> <a-form-item>
<a-select-option value="wechat-video">wechat-videocamouflage WeChat video</a-select-option> <a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="dtls">dtlscamouflage DTLS 1.2 packages</a-select-option> <a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="wireguard">wireguardcamouflage wireguard packages</a-select-option> <a-select-option value="srtp">srtp (video call)</a-select-option>
</a-select> <a-select-option value="utp">utp (BT download)</a-select-option>
</a-form-item> <a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-form-item label='{{ i18n "password" }}'> <a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-input v-model.number="inbound.stream.kcp.seed"></a-input> <a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-form-item> </a-select>
<a-form-item label="mtu"> </a-form-item>
<a-input type="number" v-model.number="inbound.stream.kcp.mtu"></a-input> </td>
</a-form-item> </tr>
<a-form-item label="tti (ms)"> <tr>
<a-input type="number" v-model.number="inbound.stream.kcp.tti"></a-input> <td>{{ i18n "password" }}</td>
</a-form-item> <td>
<a-form-item label="uplink capacity (MB/S)"> <a-form-item>
<a-input type="number" v-model.number="inbound.stream.kcp.upCap"></a-input> <a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="downlink capacity (MB/S)"> </td>
<a-input type="number" v-model.number="inbound.stream.kcp.downCap"></a-input> </tr>
</a-form-item> <tr>
<a-form-item label="congestion"> <td>mtu</td>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <td>
</a-form-item> <a-form-item>
<a-form-item label="read buffer size (MB)"> <a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
<a-input type="number" v-model.number="inbound.stream.kcp.readBuffer"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label="write buffer size (MB)"> </tr>
<a-input type="number" v-model.number="inbound.stream.kcp.writeBuffer"></a-input> <tr>
</a-form-item> <td>tti (ms)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uplink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>downlink capacity (MB/S)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>congestion</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>read buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>write buffer size (MB)</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,24 +1,41 @@
{{define "form/streamQUIC"}} {{define "form/streamQUIC"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="none">none</a-select-option> <td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option> <td>
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option> <a-form-item>
</a-select> <a-select v-model="inbound.stream.quic.security" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-form-item> <a-select-option value="none">none</a-select-option>
<a-form-item label='{{ i18n "password" }}'> <a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
<a-input v-model.trim="inbound.stream.quic.key"></a-input> <a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
</a-form-item> </a-select>
<a-form-item label='{{ i18n "camouflage" }}'> </a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> </td>
<a-select-option value="none">nonenot camouflage</a-select-option> </tr>
<a-select-option value="srtp">srtpcamouflage video call</a-select-option> <tr>
<a-select-option value="utp">utpcamouflage BT download</a-select-option> <td>{{ i18n "password" }}</td>
<a-select-option value="wechat-video">wechat-videocamouflage WeChat video</a-select-option> <td>
<a-select-option value="dtls">dtlscamouflage DTLS 1.2 packages</a-select-option> <a-form-item>
<a-select-option value="wireguard">wireguardcamouflage wireguard packages</a-select-option> <a-input v-model.trim="inbound.stream.quic.key" style="width: 200px;"></a-input>
</a-select> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "camouflage" }}</td>
<td>
<a-form-item>
<a-select v-model="inbound.stream.quic.type" style="width: 200px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="none">none (not camouflage)</a-select-option>
<a-select-option value="srtp">srtp (video call)</a-select-option>
<a-select-option value="utp">utp (BT download)</a-select-option>
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,16 +1,24 @@
{{define "form/streamSettings"}} {{define "form/streamSettings"}}
<!-- select stream network --> <!-- select stream network -->
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label='{{ i18n "transmission" }}'> <table width="100%" class="ant-table-tbody">
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <tr>
<a-select-option value="tcp">tcp</a-select-option> <td>{{ i18n "transmission" }}</td>
<a-select-option value="kcp">kcp</a-select-option> <td>
<a-select-option value="ws">ws</a-select-option> <a-form-item>
<a-select-option value="http">http</a-select-option> <a-select v-model="inbound.stream.network" @change="streamNetworkChange"
<a-select-option value="quic">quic</a-select-option> :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option value="grpc">grpc</a-select-option> <a-select-option value="tcp">tcp</a-select-option>
</a-select> <a-select-option value="kcp">kcp</a-select-option>
</a-form-item> <a-select-option value="ws">ws</a-select-option>
<a-select-option value="http">http</a-select-option>
<a-select-option value="quic">quic</a-select-option>
<a-select-option value="grpc">grpc</a-select-option>
</a-select>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
<!-- tcp --> <!-- tcp -->

View File

@@ -13,74 +13,109 @@
</a-form> </a-form>
<!-- tcp request --> <!-- tcp request -->
<a-form v-if="inbound.stream.tcp.type === 'http'" <a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
layout="inline"> <table width="100%" class="ant-table-tbody">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'> <tr>
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input> <td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'> <a-form-item>
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input> <a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestPath" }}'> </td>
<a-row v-for="(path, index) in inbound.stream.tcp.request.path"> </tr>
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input> <tr>
</a-row> <td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> <a-form-item>
<a-row> <a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
<a-button size="small" </a-form-item>
@click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')"> </td>
+ </tr>
</a-button> <tr>
</a-row> <td>{{ i18n "pages.inbounds.stream.tcp.requestPath" }}</td>
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers"> <td>
<a-input style="width: 50%" v-model.trim="header.name" <a-form-item>
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input> <a-row v-for="(path, index) in inbound.stream.tcp.request.path">
<a-input style="width: 50%" v-model.trim="header.value" <a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;"></a-input>
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-row>
<template slot="addonAfter"> </a-form-item>
<a-button size="small" </td>
@click="inbound.stream.tcp.request.removeHeader(index)"> </tr>
- <tr>
</a-button> <td colspan="2">
</template> <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
</a-input> <a-row>
</a-input-group> <a-button size="small"
</a-form-item> @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
</a-form> +
</a-button>
<!-- tcp response --> </a-row>
<a-form v-if="inbound.stream.tcp.type === 'http'" <a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
layout="inline"> <a-input style="width: 50%" v-model.trim="header.name"
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'> addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input> <a-input style="width: 50%" v-model.trim="header.value"
</a-form-item> addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}'> <template slot="addonAfter">
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input> <a-button size="small"
</a-form-item> @click="inbound.stream.tcp.request.removeHeader(index)">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'> -
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input> </a-button>
</a-form-item> </template>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'> </a-input>
<a-row> </a-input-group>
<a-button size="small" </a-form-item>
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')"> </td>
+ </tr>
</a-button> <!-- tcp response -->
</a-row> <tr>
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers"> <td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
<a-input style="width: 50%" v-model.trim="header.name" <td>
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input> <a-form-item>
<a-input style="width: 50%" v-model.trim="header.value" <a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> </a-form-item>
<template slot="addonAfter"> </td>
<a-button size="small" </tr>
@click="inbound.stream.tcp.response.removeHeader(index)"> <tr>
- <td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
</a-button> <td>
</template> <a-form-item>
</a-input> <a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
</a-input-group> </a-form-item>
</a-form-item> </td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
<a-row>
<a-button size="small"
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
+
</a-button>
</a-row>
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
<a-input style="width: 50%" v-model.trim="header.name"
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.tcp.response.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,33 +1,47 @@
{{define "form/streamWS"}} {{define "form/streamWS"}}
<a-form layout="inline"> <a-form layout="inline">
<a-form-item label="acceptProxyProtocol"> <table width="100%" class="ant-table-tbody">
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch> <tr>
</a-form-item> <td>acceptProxyProtocol</td>
</a-form> <td>
<a-form layout="inline"> <a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
<a-input v-model.trim="inbound.stream.ws.path"></a-input> </a-form-item>
</a-form-item> </td>
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'> </tr>
<a-row> <tr>
<a-button size="small" <td>{{ i18n "path" }}</td>
@click="inbound.stream.ws.addHeader('Host', '')"> <td>
+ <a-form-item>
</a-button> <a-input v-model.trim="inbound.stream.ws.path" style="width: 250px;"></a-input>
</a-row> </a-form-item>
<a-input-group v-for="(header, index) in inbound.stream.ws.headers"> </td>
<a-input style="width: 50%" v-model.trim="header.name" </tr>
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input> <tr>
<a-input style="width: 50%" v-model.trim="header.value" <td colspan="2">
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
<template slot="addonAfter"> <a-row>
<a-button size="small" <a-button size="small"
@click="inbound.stream.ws.removeHeader(index)"> @click="inbound.stream.ws.addHeader('Host', '')">
- +
</a-button> </a-button>
</template> </a-row>
</a-input> <a-input-group v-for="(header, index) in inbound.stream.ws.headers">
</a-input-group> <a-input style="width: 50%" v-model.trim="header.name"
</a-form-item> addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
<a-input style="width: 50%" v-model.trim="header.value"
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
<template slot="addonAfter">
<a-button size="small"
@click="inbound.stream.ws.removeHeader(index)">
-
</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -1,75 +1,241 @@
{{define "form/tlsSettings"}} {{define "form/tlsSettings"}}
<!-- tls enable --> <!-- tls enable -->
<a-form layout="inline" v-if="inbound.canSetTls()"> <a-form v-if="inbound.canSetTls()" layout="inline">
<a-form-item label="tls"> <a-form-item label="TLS">
<a-switch v-model="inbound.tls"> <a-switch v-model="inbound.tls">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
<a-form-item v-if="inbound.canEnableXTls()" label="xtls"> <a-form-item v-if="inbound.canEnableReality()" label="Reality">
<a-switch v-model="inbound.xtls"></a-switch> <a-switch v-model="inbound.reality"></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"> <table width="100%" class="ant-table-tbody">
<a-input v-model.trim="inbound.stream.tls.settings[0].serverName"></a-input> <tr>
</a-form-item> <td>SNI</td>
<a-form-item label="CipherSuites"> <td>
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px"> <a-form-item placeholder="Server Name Indication" v-if="inbound.tls">
<a-select-option value="">auto</a-select-option> <a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option> </a-form-item>
</a-select> </td>
</a-form-item> </tr>
<a-form-item label="MinVersion"> <tr>
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <td>CipherSuites</td>
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-form-item label="MaxVersion"> <a-select-option value="">auto</a-select-option>
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select-option v-for="key in TLS_CIPHER_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> </td>
<a-form-item label="uTLS" v-if="inbound.tls" > </tr>
<a-select v-model="inbound.stream.tls.settings[0].fingerprint" style="width: 135px"> <tr>
<a-select-option value=''>None</a-select-option> <td>MinVersion</td>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <td>
</a-select> <a-form-item>
</a-form-item> <a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-form-item label='{{ i18n "domainName" }}'> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-input v-model.trim="inbound.stream.tls.server"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="Alpn"> </td>
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px"> </tr>
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox> <tr>
</a-checkbox-group> <td>MaxVersion</td>
</a-form-item> <td>
<a-form-item label="Allow insecure"> <a-form-item>
<a-switch v-model="inbound.stream.tls.settings[0].allowInsecure"></a-switch> <a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
</a-form-item> <a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
<a-form-item label='{{ i18n "certificate" }}'> </a-select>
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid"> </a-form-item>
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button> </td>
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button> </tr>
</a-radio-group> <tr>
</a-form-item> <td>uTLS</td>
<template v-if="inbound.stream.tls.certs[0].useFile"> <td>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'> <a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input> <a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
</a-form-item> <a-select-option value=''>None</a-select-option>
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input> </a-select>
</a-form-item> </a-form-item>
<a-button @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button> </td>
</tr>
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Alpn</td>
<td>
<a-form-item>
<a-checkbox-group v-model="inbound.stream.tls.alpn">
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
</a-checkbox-group>
</a-form-item>
</td>
</tr>
<tr>
<td>Allow insecure</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td colspan="2">
<a-form-item label="{{ i18n "certificate" }}">
<a-radio-group v-model="inbound.stream.tls.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>
</td>
</tr>
<template v-if="inbound.stream.tls.certs[0].useFile">
<tr>
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:250px;"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
</td>
</tr>
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'> <tr>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input> <td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
</a-form-item> <td>
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'> <a-form-item>
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input> <a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
</a-form-item> </a-form-item>
</td>
</tr>
<tr>
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
<td>
<a-form-item>
<a-input type="textarea" :rows="3" style="width:250px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
</a-form-item>
</td>
</tr>
</template> </template>
</table>
</a-form>
<!-- reality settings -->
<a-form v-if="inbound.reality" layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "domainName" }}</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Show</td>
<td>
<a-form-item>
<a-switch v-model="inbound.stream.reality.show"></a-switch>
</a-form-item>
</td>
</tr>
<tr>
<td>Xver</td>
<td>
<a-form-item>
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item>
</td>
</tr>
<tr>
<td>uTLS</td>
<td>
<a-form-item >
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
<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>
</td>
</tr>
<tr>
<td>Dest</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Server Names</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Short Ids</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>SpiderX</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Private Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td>Public Key</td>
<td>
<a-form-item>
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
</a-form-item>
</td>
</tr>
<tr>
<td></td>
<td>
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
</td>
</tr>
</table>
</a-form> </a-form>
{{end}} {{end}}

View File

@@ -49,9 +49,9 @@
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.reality">
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br /> reality: <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> reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td> </td>
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag> <td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
</td> </td>

View File

@@ -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 = '';

View File

@@ -132,7 +132,7 @@
<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.isReality" color="cyan">reality</a-tag>
</template> </template>
</template> </template>
<template slot="clients" slot-scope="text, dbInbound"> <template slot="clients" slot-scope="text, dbInbound">
@@ -209,11 +209,10 @@
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script> <script>
const columns = [{ const columns = [{
title: '{{ i18n "pages.inbounds.operate" }}', title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center', align: 'center',
width: 30, width: 40,
scopedSlots: { customRender: 'action' }, scopedSlots: { customRender: 'action' },
}, { }, {
title: '{{ i18n "pages.inbounds.enable" }}', title: '{{ i18n "pages.inbounds.enable" }}',
@@ -221,7 +220,7 @@
width: 40, width: 40,
scopedSlots: { customRender: 'enable' }, scopedSlots: { customRender: 'enable' },
}, { }, {
title: "Id", title: "ID",
align: 'center', align: 'center',
dataIndex: "id", dataIndex: "id",
width: 30, width: 30,
@@ -529,9 +528,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
@@ -543,9 +542,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();
}, },
}); });
@@ -559,9 +558,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
@@ -571,17 +570,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);
}, },

View File

@@ -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
if err != nil { }
return "", 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 { func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) {
if emails[client.Email] { 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 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) {
@@ -201,14 +225,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
@@ -231,8 +247,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
} }
@@ -241,29 +261,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()
@@ -289,21 +315,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
} }
@@ -313,18 +331,45 @@ 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
} }
} 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)

View File

@@ -194,9 +194,11 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
versions := make([]string, 0, len(releases)) var versions []string
for _, release := range releases { for _, release := range releases {
versions = append(versions, release.TagName) if release.TagName >= "v1.8.0" {
versions = append(versions, release.TagName)
}
} }
return versions, nil return versions, nil
} }
@@ -390,3 +392,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
}

View File

@@ -39,7 +39,7 @@
"depleted" = "Depleted" "depleted" = "Depleted"
"depletingSoon" = "Depleting soon" "depletingSoon" = "Depleting soon"
"domainName" = "Domain name" "domainName" = "Domain name"
"additional" = "Alter" "additional" = "Alter ID"
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Certificat" "certificate" = "Certificat"
"fail" = "Fail" "fail" = "Fail"
@@ -156,7 +156,7 @@
"first" = "First" "first" = "First"
"last" = "Last" "last" = "Last"
"prefix" = "Prefix" "prefix" = "Prefix"
"postfix" = "postfix" "postfix" = "Postfix"
"delayedStart" = "Start after first use" "delayedStart" = "Start after first use"
"expireDays" = "Expire days" "expireDays" = "Expire days"
"days" = "day(s)" "days" = "day(s)"

View File

@@ -117,7 +117,7 @@
"network" = "شبکه" "network" = "شبکه"
"destinationPort" = "پورت مقصد" "destinationPort" = "پورت مقصد"
"targetAddress" = "آدرس مقصد" "targetAddress" = "آدرس مقصد"
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید" "disableInsecureEncryption" = "غیرفعال سازی رمزگذاری ناامن"
"monitorDesc" = "به طور پیش فرض خالی بگذارید" "monitorDesc" = "به طور پیش فرض خالی بگذارید"
"meansNoLimit" = "یعنی بدون محدودیت" "meansNoLimit" = "یعنی بدون محدودیت"
"totalFlow" = "کل ترافیک" "totalFlow" = "کل ترافیک"

View File

@@ -39,7 +39,7 @@
"depleted" = "耗尽" "depleted" = "耗尽"
"depletingSoon" = "即将耗尽" "depletingSoon" = "即将耗尽"
"domainName" = "域名" "domainName" = "域名"
"additional" = "额外" "additional" = "额外 ID"
"monitor" = "监听" "monitor" = "监听"
"certificate" = "证书" "certificate" = "证书"
"fail" = "失败" "fail" = "失败"