Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorMHSanaei <ho3ein.sanaei@gmail.com>2023-05-06 19:51:14 +0300
committerMHSanaei <ho3ein.sanaei@gmail.com>2023-05-06 19:51:14 +0300
commitf22dd6b53d736556377080a305c40be2db3c8cce (patch)
treecaae9937645094ccf2513ac46a2bfa5ca446df3b /web
parent735df6bd4ed18d656355067826a6f5e50495c63e (diff)
[feature] multi-user shadowsocks @alireza0
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
Diffstat (limited to 'web')
-rw-r--r--web/assets/js/model/xray.js162
-rw-r--r--web/assets/js/util/utils.js6
-rw-r--r--web/html/xui/client_modal.html11
-rw-r--r--web/html/xui/form/client.html3
-rw-r--r--web/html/xui/form/protocol/shadowsocks.html83
-rw-r--r--web/html/xui/form/protocol/trojan.html6
-rw-r--r--web/html/xui/form/protocol/vless.html6
-rw-r--r--web/html/xui/form/protocol/vmess.html6
-rw-r--r--web/html/xui/inbound_modal.html1
-rw-r--r--web/html/xui/inbounds.html30
-rw-r--r--web/service/inbound.go5
-rw-r--r--web/service/sub.go24
12 files changed, 254 insertions, 89 deletions
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js
index c8b02641..22573a23 100644
--- a/web/assets/js/model/xray.js
+++ b/web/assets/js/model/xray.js
@@ -1012,66 +1012,6 @@ class Inbound extends XrayCommonClass {
return this.network === "http";
}
- // VMess & VLess
- get uuid() {
- switch (this.protocol) {
- case Protocols.VMESS:
- return this.settings.vmesses[0].id;
- case Protocols.VLESS:
- return this.settings.vlesses[0].id;
- default:
- return "";
- }
- }
-
- // VLess & Trojan
- get flow() {
- switch (this.protocol) {
- case Protocols.VLESS:
- return this.settings.vlesses[0].flow;
- case Protocols.TROJAN:
- return this.settings.trojans[0].flow;
- default:
- return "";
- }
- }
-
- // VMess
- get alterId() {
- switch (this.protocol) {
- case Protocols.VMESS:
- return this.settings.vmesses[0].alterId;
- default:
- return "";
- }
- }
-
- // Socks & HTTP
- get username() {
- switch (this.protocol) {
- case Protocols.SOCKS:
- case Protocols.HTTP:
- return this.settings.accounts[0].user;
- default:
- return "";
- }
- }
-
- // Trojan & Shadowsocks & Socks & HTTP
- get password() {
- switch (this.protocol) {
- case Protocols.TROJAN:
- return this.settings.trojans[0].password;
- case Protocols.SHADOWSOCKS:
- return this.settings.password;
- case Protocols.SOCKS:
- case Protocols.HTTP:
- return this.settings.accounts[0].pass;
- default:
- return "";
- }
- }
-
// Shadowsocks
get method() {
switch (this.protocol) {
@@ -1146,9 +1086,13 @@ class Inbound extends XrayCommonClass {
return this.settings.vlesses[index].expiryTime < new Date().getTime();
return false
case Protocols.TROJAN:
- if(this.settings.trojans[index].expiryTime > 0)
- return this.settings.trojans[index].expiryTime < new Date().getTime();
- return false
+ if(this.settings.trojans[index].expiryTime > 0)
+ return this.settings.trojans[index].expiryTime < new Date().getTime();
+ return false
+ case Protocols.SHADOWSOCKS:
+ if(this.settings.shadowsockses[index].expiryTime > 0)
+ return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
+ return false
default:
return false;
}
@@ -1159,7 +1103,6 @@ class Inbound extends XrayCommonClass {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
- case Protocols.SHADOWSOCKS:
break;
default:
return false;
@@ -1228,7 +1171,6 @@ class Inbound extends XrayCommonClass {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
- case Protocols.SHADOWSOCKS:
return true;
default:
return false;
@@ -1443,13 +1385,11 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
- genSSLink(address='', remark='') {
+ genSSLink(address='', remark='', clientIndex = 0) {
let settings = this.settings;
- const server = this.stream.tls.server;
- if (!ObjectUtil.isEmpty(server)) {
- address = server;
- }
- return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`;
+ const port = this.port;
+
+ return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark);
}
genTrojanLink(address = '', remark = '', clientIndex = 0) {
@@ -1569,7 +1509,11 @@ class Inbound extends XrayCommonClass {
remark += '-' + this.settings.vlesses[clientIndex].email
}
return this.genVLESSLink(address, remark, clientIndex);
- case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark);
+ case Protocols.SHADOWSOCKS:
+ if (this.settings.shadowsockses[clientIndex].email != ""){
+ remark = this.settings.shadowsockses[clientIndex].email
+ }
+ return this.genSSLink(address, remark, clientIndex);
case Protocols.TROJAN:
if (this.settings.trojans[clientIndex].email != ""){
remark += '-' + this.settings.trojans[clientIndex].email
@@ -2033,13 +1977,15 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
Inbound.ShadowsocksSettings = class extends Inbound.Settings {
constructor(protocol,
method=SSMethods.BLAKE3_AES_256_GCM,
- password=RandomUtil.randomSeq(44),
- network='tcp,udp'
+ password=RandomUtil.randomShadowsocksPassword(),
+ network='tcp,udp',
+ shadowsockses=[new Inbound.ShadowsocksSettings.Shadowsocks()]
) {
super(protocol);
this.method = method;
this.password = password;
this.network = network;
+ this.shadowsockses = shadowsockses;
}
static fromJson(json={}) {
@@ -2048,6 +1994,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
json.method,
json.password,
json.network,
+ json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
);
}
@@ -2056,10 +2003,77 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
method: this.method,
password: this.password,
network: this.network,
+ clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses)
};
}
};
+Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
+ constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
+ super();
+ this.password = password;
+ this.email = email;
+ this.limitIp = limitIp;
+ this.totalGB = totalGB;
+ this.expiryTime = expiryTime;
+ this.enable = enable;
+ this.tgId = tgId;
+ this.subId = subId;
+ }
+
+ toJson() {
+ return {
+ password: this.password,
+ email: this.email,
+ limitIp: this.limitIp,
+ totalGB: this.totalGB,
+ expiryTime: this.expiryTime,
+ enable: this.enable,
+ tgId: this.tgId,
+ subId: this.subId,
+ };
+ }
+
+ static fromJson(json = {}) {
+ return new Inbound.ShadowsocksSettings.Shadowsocks(
+ json.password,
+ json.email,
+ json.limitIp,
+ json.totalGB,
+ json.expiryTime,
+ json.enable,
+ json.tgId,
+ json.subId,
+ );
+ }
+
+ get _expiryTime() {
+ if (this.expiryTime === 0 || this.expiryTime === "") {
+ return null;
+ }
+ if (this.expiryTime < 0){
+ return this.expiryTime / -86400000;
+ }
+ return moment(this.expiryTime);
+ }
+
+ set _expiryTime(t) {
+ if (t == null || t === "") {
+ this.expiryTime = 0;
+ } else {
+ this.expiryTime = t.valueOf();
+ }
+ }
+ get _totalGB() {
+ return toFixed(this.totalGB / ONE_GB, 2);
+ }
+
+ set _totalGB(gb) {
+ this.totalGB = toFixed(gb * ONE_GB, 0);
+ }
+
+};
+
Inbound.DokodemoSettings = class extends Inbound.Settings {
constructor(protocol, address, port, network='tcp,udp', followRedirect=false) {
super(protocol);
diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js
index 451f63e9..9c441537 100644
--- a/web/assets/js/util/utils.js
+++ b/web/assets/js/util/utils.js
@@ -165,6 +165,12 @@ class RandomUtil {
str += this.randomShortIdSeq(8)
return str;
}
+
+ static randomShadowsocksPassword(){
+ let array = new Uint8Array(32);
+ window.crypto.getRandomValues(array);
+ return btoa(String.fromCharCode.apply(null, array));
+ }
}
class ObjectUtil {
diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html
index bf4ed92a..e459b520 100644
--- a/web/html/xui/client_modal.html
+++ b/web/html/xui/client_modal.html
@@ -44,7 +44,7 @@
if (this.clients[index].expiryTime < 0){
this.delayedStart = true;
}
- this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id;
+ this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]);
} else {
this.addClient(this.inbound.protocol, this.clients);
}
@@ -56,14 +56,23 @@
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
+ case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
+ getClientId(protocol, client) {
+ switch(protocol){
+ case Protocols.TROJAN: return client.password;
+ case Protocols.SHADOWSOCKS: return client.email;
+ default: return client.id;
+ }
+ },
addClient(protocol, clients) {
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());
+ case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
default: return null;
}
},
diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html
index 501861db..1740e74b 100644
--- a/web/html/xui/form/client.html
+++ b/web/html/xui/form/client.html
@@ -18,7 +18,8 @@
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch>
</a-form-item>
- <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN">
+ <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
+ <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
<a-input v-model.trim="client.password" style="width: 150px;" ></a-input>
</a-form-item>
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
diff --git a/web/html/xui/form/protocol/shadowsocks.html b/web/html/xui/form/protocol/shadowsocks.html
index 718ba894..3c63fe01 100644
--- a/web/html/xui/form/protocol/shadowsocks.html
+++ b/web/html/xui/form/protocol/shadowsocks.html
@@ -1,5 +1,87 @@
{{define "form/shadowsocks"}}
<a-form layout="inline">
+ <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
+ <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
+ <a-form-item>
+ <span slot="label">
+ <span>{{ i18n "pages.inbounds.Email" }}</span>
+ <a-tooltip>
+ <template slot="title">
+ <span>{{ i18n "pages.inbounds.EmailDesc" }}</span>
+ </template>
+ <a-icon @click="getNewEmail(client)" type="sync"></a-icon>
+ </a-tooltip>
+ </span>
+ <a-input v-model.trim="client.email" style="width: 150px;"></a-input>
+ </a-form-item>
+ <a-form-item label="Password">
+ <a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
+ <a-input v-model.trim="client.password" style="width: 150px;"></a-input>
+ </a-form-item>
+ <a-form-item label="Subscription" v-if="client.email">
+ <a-input v-model.trim="client.subId"></a-input>
+ </a-form-item>
+ <a-form-item label="Telegram Username" v-if="client.email">
+ <a-input v-model.trim="client.tgId"></a-input>
+ </a-form-item>
+ <a-form-item>
+ <span slot="label">
+ <span>{{ i18n "pages.inbounds.IPLimit" }}</span>
+ <a-tooltip>
+ <template slot="title">
+ <span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
+ </template>
+ <a-icon type="question-circle" theme="filled"></a-icon>
+ </a-tooltip>
+ </span>
+ <a-input-number v-model="client.limitIp" min="0" style="width: 70px;"></a-input-number>
+ </a-form-item>
+ <a-form-item>
+ <span slot="label">
+ <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>
+ </span>
+ <a-input-number v-model="client._totalGB" :min="0"></a-input-number>
+ </a-form-item>
+ <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
+ <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
+ </a-form-item>
+ <a-form-item label='{{ i18n "pages.client.expireDays" }}'>
+ <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
+ </a-form-item>
+ <a-form-item>
+ <span slot="label">
+ <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>
+ </span>
+ <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
+ :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
+ v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
+ </a-form-item>
+ </a-collapse-panel>
+ </a-collapse>
+ <a-collapse v-else>
+ <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
+ <table width="100%">
+ <tr class="client-table-header">
+ <th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
+ </tr>
+ <tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
+ <td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
+ </tr>
+ </table>
+ </a-collapse-panel>
+ </a-collapse>
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
@@ -15,5 +97,4 @@
<a-select-option value="udp">UDP</a-select-option>
</a-select>
</a-form-item>
-</a-form>
{{end}} \ No newline at end of file
diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html
index e63943e6..1581b07c 100644
--- a/web/html/xui/form/protocol/trojan.html
+++ b/web/html/xui/form/protocol/trojan.html
@@ -53,6 +53,12 @@
</span>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
+ <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
+ <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
+ </a-form-item>
+ <a-form-item label='{{ i18n "pages.client.expireDays" }}'>
+ <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
+ </a-form-item>
<a-form-item>
<span slot="label">
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html
index 33e0c170..023c04b9 100644
--- a/web/html/xui/form/protocol/vless.html
+++ b/web/html/xui/form/protocol/vless.html
@@ -59,6 +59,12 @@
</span>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
+ <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
+ <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
+ </a-form-item>
+ <a-form-item label='{{ i18n "pages.client.expireDays" }}'>
+ <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
+ </a-form-item>
<a-form-item>
<span slot="label">
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html
index 6471e20d..62b80468 100644
--- a/web/html/xui/form/protocol/vmess.html
+++ b/web/html/xui/form/protocol/vmess.html
@@ -50,6 +50,12 @@
</span>
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
</a-form-item>
+ <a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
+ <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
+ </a-form-item>
+ <a-form-item label='{{ i18n "pages.client.expireDays" }}'>
+ <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
+ </a-form-item>
<a-form-item>
<span slot="label">
<span >{{ i18n "pages.inbounds.expireDate" }}</span>
diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html
index 966de8dd..7c338e26 100644
--- a/web/html/xui/inbound_modal.html
+++ b/web/html/xui/inbound_modal.html
@@ -48,6 +48,7 @@
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
+ case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html
index 2792eb75..5d7e041f 100644
--- a/web/html/xui/inbounds.html
+++ b/web/html/xui/inbounds.html
@@ -116,15 +116,11 @@
<a-dropdown :trigger="['click']">
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
- <a-menu-item v-if="dbInbound.isSS" key="qrcode">
- <a-icon type="qrcode"></a-icon>
- {{ i18n "qrCode" }}
- </a-menu-item>
<a-menu-item key="edit">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
- <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess">
+ <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS">
<a-menu-item key="addClient">
<a-icon type="user-add"></a-icon>
{{ i18n "pages.client.add"}}
@@ -168,7 +164,7 @@
</template>
<template slot="protocol" slot-scope="text, dbInbound">
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
- <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
+ <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan">
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
<a-tag style="margin:0;" 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>
@@ -231,7 +227,7 @@
{{template "client_table"}}
</a-table>
<a-table
- v-else-if="record.protocol === Protocols.TROJAN"
+ v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS"
:row-key="client => client.id"
:columns="innerTrojanColumns"
:data-source="getInboundClients(record)"
@@ -671,7 +667,7 @@
},
delClient(dbInboundId,client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
- clientId = dbInbound.protocol == "trojan" ? client.password : client.id;
+ clientId = this.getClientId(dbInbound.protocol,client);
this.$confirm({
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
@@ -686,9 +682,17 @@
case Protocols.VMESS: return clientSettings.vmesses;
case Protocols.VLESS: return clientSettings.vlesses;
case Protocols.TROJAN: return clientSettings.trojans;
+ case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
default: return null;
}
},
+ getClientId(protocol, client) {
+ switch(protocol){
+ case Protocols.TROJAN: return client.password;
+ case Protocols.SHADOWSOCKS: return client.email;
+ default: return client.id;
+ }
+ },
showQrcode(dbInbound, clientIndex) {
const link = dbInbound.genLink(clientIndex);
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound);
@@ -707,7 +711,7 @@
clients = this.getClients(dbInbound.protocol, inbound.settings);
index = this.findIndexOfClient(clients, client);
clients[index].enable = !clients[index].enable;
- clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id;
+ clientId = this.getClientId(dbInbound.protocol,clients[index]);
await this.updateClient(clients[index],dbInboundId, clientId);
this.loading(false);
},
@@ -719,11 +723,13 @@
},
getInboundClients(dbInbound) {
if(dbInbound.protocol == Protocols.VLESS) {
- return dbInbound.toInbound().settings.vlesses
+ return dbInbound.toInbound().settings.vlesses;
} else if(dbInbound.protocol == Protocols.VMESS) {
- return dbInbound.toInbound().settings.vmesses
+ return dbInbound.toInbound().settings.vmesses;
} else if(dbInbound.protocol == Protocols.TROJAN) {
- return dbInbound.toInbound().settings.trojans
+ return dbInbound.toInbound().settings.trojans;
+ } else if(dbInbound.protocol == Protocols.SHADOWSOCKS) {
+ return dbInbound.toInbound().settings.shadowsockses;
}
},
resetClientTraffic(client,dbInboundId) {
diff --git a/web/service/inbound.go b/web/service/inbound.go
index b14f7b4f..7e838b5f 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -332,6 +332,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
if oldInbound.Protocol == "trojan" {
client_key = "password"
}
+ if oldInbound.Protocol == "shadowsocks" {
+ client_key = "email"
+ }
inerfaceClients := settings["clients"].([]interface{})
var newClients []interface{}
@@ -398,6 +401,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
oldClientId := ""
if oldInbound.Protocol == "trojan" {
oldClientId = oldClient.Password
+ } else if oldInbound.Protocol == "shadowsocks" {
+ oldClientId = oldClient.Email
} else {
oldClientId = oldClient.ID
}
diff --git a/web/service/sub.go b/web/service/sub.go
index 69cafda1..3f8b7685 100644
--- a/web/service/sub.go
+++ b/web/service/sub.go
@@ -97,6 +97,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
return s.genVlessLink(inbound, email)
case "trojan":
return s.genTrojanLink(inbound, email)
+ case "shadowsocks":
+ return s.genShadowsocksLink(inbound, email)
}
return ""
}
@@ -565,6 +567,28 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
return url.String()
}
+func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
+ address := s.address
+ if inbound.Protocol != model.Shadowsocks {
+ return ""
+ }
+ clients, _ := s.inboundService.getClients(inbound)
+
+ var settings map[string]interface{}
+ json.Unmarshal([]byte(inbound.Settings), &settings)
+ inboundPassword := settings["password"].(string)
+ method := settings["method"].(string)
+ clientIndex := -1
+ for i, client := range clients {
+ if client.Email == email {
+ clientIndex = i
+ break
+ }
+ }
+ encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
+ return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email)
+}
+
func searchKey(data interface{}, key string) (interface{}, bool) {
switch val := data.(type) {
case map[string]interface{}: