mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-14 13:31:41 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402c713f06 | ||
|
|
a371bec2aa | ||
|
|
427d008bd1 | ||
|
|
e69d17be67 | ||
|
|
09c61976ea | ||
|
|
6e17c282e0 | ||
|
|
700973655c | ||
|
|
dcb54267f2 | ||
|
|
19e851d5c8 | ||
|
|
3f7ef07b8e | ||
|
|
89be2c8fec | ||
|
|
dd11585074 | ||
|
|
7ecb73af8c |
@@ -1 +1 @@
|
||||
1.1.1
|
||||
1.1.3
|
||||
@@ -212,6 +212,7 @@
|
||||
.ant-card-dark .ant-modal-close,
|
||||
.ant-card-dark i,
|
||||
.ant-card-dark .ant-select-dropdown-menu-item,
|
||||
.ant-card-dark .ant-calendar-day-select,
|
||||
.ant-card-dark .ant-calendar-month-select,
|
||||
.ant-card-dark .ant-calendar-year-select,
|
||||
.ant-card-dark .ant-calendar-date,
|
||||
@@ -226,7 +227,7 @@
|
||||
.ant-card-dark .ant-calendar-date:hover,
|
||||
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||
background-color: #004488;
|
||||
background-color: #11314d;
|
||||
}
|
||||
|
||||
.ant-card-dark tbody .ant-table-expanded-row,
|
||||
@@ -243,7 +244,7 @@
|
||||
.ant-card-dark .ant-select-selection,
|
||||
.ant-card-dark .ant-calendar-picker-clear {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #2e3b52;
|
||||
background-color: #193752;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-select-disabled .ant-select-selection {
|
||||
@@ -264,12 +265,19 @@
|
||||
|
||||
.ant-card-dark .ant-modal-content,
|
||||
.ant-card-dark .ant-modal-body,
|
||||
.ant-card-dark .ant-modal-header,
|
||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||
.ant-card-dark .ant-modal-header {
|
||||
color: hsla(0,0%,100%,.65);
|
||||
background-color: #222a37;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-calendar-selected-day .ant-calendar-date {
|
||||
background-color: #1668dc;
|
||||
}
|
||||
|
||||
.ant-card-dark .ant-calendar-time-picker-select li:hover {
|
||||
background: #1668dc;
|
||||
}
|
||||
|
||||
.client-table-header {
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
@@ -29,20 +29,6 @@ const SSMethods = {
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
};
|
||||
|
||||
const RULE_IP = {
|
||||
PRIVATE: 'geoip:private',
|
||||
CN: 'geoip:cn',
|
||||
};
|
||||
|
||||
const RULE_DOMAIN = {
|
||||
ADS: 'geosite:category-ads',
|
||||
ADS_ALL: 'geosite:category-ads-all',
|
||||
CN: 'geosite:cn',
|
||||
GOOGLE: 'geosite:google',
|
||||
FACEBOOK: 'geosite:facebook',
|
||||
SPEEDTEST: 'geosite:speedtest',
|
||||
};
|
||||
|
||||
const TLS_FLOW_CONTROL = {
|
||||
VISION: "xtls-rprx-vision",
|
||||
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||
@@ -89,20 +75,25 @@ const UTLS_FINGERPRINT = {
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
H3: "h3",
|
||||
H2: "h2",
|
||||
HTTP1: "http/1.1",
|
||||
H2: "h2",
|
||||
H3: "h3",
|
||||
};
|
||||
|
||||
const SNIFFING_OPTION = {
|
||||
HTTP: "http",
|
||||
TLS: "tls",
|
||||
QUIC: "quic",
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(VmessMethods);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(RULE_IP);
|
||||
Object.freeze(RULE_DOMAIN);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
Object.freeze(TLS_CIPHER_OPTION);
|
||||
Object.freeze(ALPN_OPTION);
|
||||
Object.freeze(SNIFFING_OPTION);
|
||||
|
||||
class XrayCommonClass {
|
||||
|
||||
@@ -451,18 +442,20 @@ class QuicStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends XrayCommonClass {
|
||||
constructor(serviceName="") {
|
||||
constructor(serviceName="", multiMode=false) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.multiMode = multiMode;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new GrpcStreamSettings(json.serviceName);
|
||||
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
serviceName: this.serviceName,
|
||||
multiMode: this.multiMode,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,7 +748,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class Sniffing extends XrayCommonClass {
|
||||
constructor(enabled=true, destOverride=['http', 'tls']) {
|
||||
constructor(enabled=true, destOverride=['http', 'tls', 'quic']) {
|
||||
super();
|
||||
this.enabled = enabled;
|
||||
this.destOverride = destOverride;
|
||||
@@ -765,7 +758,7 @@ class Sniffing extends XrayCommonClass {
|
||||
let destOverride = ObjectUtil.clone(json.destOverride);
|
||||
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
||||
if (ObjectUtil.isEmpty(destOverride[0])) {
|
||||
destOverride = ['http', 'tls'];
|
||||
destOverride = ['http', 'tls', 'quic'];
|
||||
}
|
||||
}
|
||||
return new Sniffing(
|
||||
@@ -1106,50 +1099,6 @@ class Inbound extends XrayCommonClass {
|
||||
if (this.protocol !== Protocols.VMESS) {
|
||||
return '';
|
||||
}
|
||||
let network = this.stream.network;
|
||||
let type = 'none';
|
||||
let host = '';
|
||||
let path = '';
|
||||
if (network === 'tcp') {
|
||||
let tcp = this.stream.tcp;
|
||||
type = tcp.type;
|
||||
if (type === 'http') {
|
||||
let request = tcp.request;
|
||||
path = request.path.join(',');
|
||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
host = request.headers[index].value;
|
||||
}
|
||||
}
|
||||
} else if (network === 'kcp') {
|
||||
let kcp = this.stream.kcp;
|
||||
type = kcp.type;
|
||||
path = kcp.seed;
|
||||
} else if (network === 'ws') {
|
||||
let ws = this.stream.ws;
|
||||
path = ws.path;
|
||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
host = ws.headers[index].value;
|
||||
}
|
||||
} else if (network === 'http') {
|
||||
network = 'h2';
|
||||
path = this.stream.http.path;
|
||||
host = this.stream.http.host.join(',');
|
||||
} else if (network === 'quic') {
|
||||
type = this.stream.quic.type;
|
||||
host = this.stream.quic.security;
|
||||
path = this.stream.quic.key;
|
||||
} else if (network === 'grpc') {
|
||||
path = this.stream.grpc.serviceName;
|
||||
}
|
||||
|
||||
if (this.stream.security === 'tls') {
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
}
|
||||
|
||||
let obj = {
|
||||
v: '2',
|
||||
ps: remark,
|
||||
@@ -1157,16 +1106,66 @@ class Inbound extends XrayCommonClass {
|
||||
port: this.port,
|
||||
id: this.settings.vmesses[clientIndex].id,
|
||||
aid: this.settings.vmesses[clientIndex].alterId,
|
||||
net: network,
|
||||
type: type,
|
||||
host: host,
|
||||
path: path,
|
||||
net: this.stream.network,
|
||||
type: 'none',
|
||||
tls: this.stream.security,
|
||||
sni: this.stream.tls.settings.serverName,
|
||||
fp: this.stream.tls.settings.fingerprint,
|
||||
alpn: this.stream.tls.alpn.join(','),
|
||||
allowInsecure: this.stream.tls.settings.allowInsecure,
|
||||
};
|
||||
let network = this.stream.network;
|
||||
if (network === 'tcp') {
|
||||
let tcp = this.stream.tcp;
|
||||
obj.type = tcp.type;
|
||||
if (tcp.type === 'http') {
|
||||
let request = tcp.request;
|
||||
obj.path = request.path.join(',');
|
||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = request.headers[index].value;
|
||||
}
|
||||
}
|
||||
} else if (network === 'kcp') {
|
||||
let kcp = this.stream.kcp;
|
||||
obj.type = kcp.type;
|
||||
obj.path = kcp.seed;
|
||||
} else if (network === 'ws') {
|
||||
let ws = this.stream.ws;
|
||||
obj.path = ws.path;
|
||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = ws.headers[index].value;
|
||||
}
|
||||
} else if (network === 'http') {
|
||||
obj.net = 'h2';
|
||||
obj.path = this.stream.http.path;
|
||||
obj.host = this.stream.http.host.join(',');
|
||||
} else if (network === 'quic') {
|
||||
obj.type = this.stream.quic.type;
|
||||
obj.host = this.stream.quic.security;
|
||||
obj.path = this.stream.quic.key;
|
||||
} else if (network === 'grpc') {
|
||||
obj.path = this.stream.grpc.serviceName;
|
||||
if (this.stream.grpc.multiMode){
|
||||
obj.type = 'multi'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stream.security === 'tls') {
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
obj.add = this.stream.tls.server;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
|
||||
obj.sni = this.stream.tls.settings.serverName;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
||||
obj.fp = this.stream.tls.settings.fingerprint;
|
||||
}
|
||||
if (this.stream.tls.alpn.length>0){
|
||||
obj.alpn = this.stream.tls.alpn.join(',');
|
||||
}
|
||||
if (this.stream.tls.settings.allowInsecure){
|
||||
obj.allowInsecure = this.stream.tls.settings.allowInsecure;
|
||||
}
|
||||
}
|
||||
|
||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
@@ -1219,6 +1218,9 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1332,6 +1334,9 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
}
|
||||
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
@@ -118,3 +119,12 @@ func (a *SettingController) restartPanel(c *gin.Context) {
|
||||
err := a.panelService.RestartPanel(time.Second * 3)
|
||||
jsonMsg(c, I18n(c, "pages.setting.restartPanel"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, defaultJsonConfig, nil)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm' }" format="YYYY-MM-DD HH:mm"
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"
|
||||
v-model="client._expiryTime" style="width: 200px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -12,5 +12,10 @@
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -9,6 +9,14 @@
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MultiMode</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -56,7 +56,8 @@
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 250px">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
@@ -76,7 +77,7 @@
|
||||
<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 v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
@@ -175,7 +176,8 @@
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item >
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 250px">
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||
</template>
|
||||
</table>
|
||||
</td></tr>
|
||||
|
||||
@@ -66,28 +66,42 @@
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<div slot="title">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
||||
<a-select v-model="refreshInterval"
|
||||
v-if="isRefreshEnabled"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''">
|
||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
<a-icon type="sync" :spin="isRefreshEnabled"></a-icon>
|
||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||
@@ -312,25 +326,22 @@
|
||||
defaultCert: '',
|
||||
defaultKey: '',
|
||||
clientCount: {},
|
||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning=true) {
|
||||
this.spinning = spinning;
|
||||
},
|
||||
async getDBInbounds() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/inbound/list');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.setInbounds(msg.obj);
|
||||
this.searchKey = '';
|
||||
},
|
||||
async getDefaultSettings() {
|
||||
this.loading();
|
||||
const msg = await HttpUtil.post('/xui/setting/defaultSettings');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
@@ -342,17 +353,16 @@
|
||||
setInbounds(dbInbounds) {
|
||||
this.inbounds.splice(0);
|
||||
this.dbInbounds.splice(0);
|
||||
this.searchedInbounds.splice(0);
|
||||
for (const inbound of dbInbounds) {
|
||||
const dbInbound = new DBInbound(inbound);
|
||||
to_inbound = dbInbound.toInbound()
|
||||
this.inbounds.push(to_inbound);
|
||||
this.dbInbounds.push(dbInbound);
|
||||
this.searchedInbounds.push(dbInbound);
|
||||
if([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol) ){
|
||||
this.clientCount[inbound.id] = this.getClientCounts(inbound,to_inbound);
|
||||
}
|
||||
}
|
||||
this.searchInbounds(this.searchKey);
|
||||
},
|
||||
getClientCounts(dbInbound,inbound){
|
||||
let clientCount = 0,active = [], deactive = [], depleted = [], expiring = [];
|
||||
@@ -785,6 +795,25 @@
|
||||
}
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}',copyText,'All-Inbounds');
|
||||
},
|
||||
async startDataRefreshLoop() {
|
||||
while (this.isRefreshEnabled) {
|
||||
try {
|
||||
await this.getDBInbounds();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(this.refreshInterval);
|
||||
}
|
||||
},
|
||||
toggleRefresh() {
|
||||
localStorage.setItem("isRefreshEnabled", this.isRefreshEnabled);
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
},
|
||||
changeRefreshInterval(){
|
||||
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
searchKey: debounce(function (newVal) {
|
||||
@@ -792,8 +821,15 @@
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
this.loading();
|
||||
this.getDefaultSettings();
|
||||
this.getDBInbounds();
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
else {
|
||||
this.getDBInbounds();
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
computed: {
|
||||
total() {
|
||||
@@ -820,7 +856,6 @@
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{{template "inboundModal"}}
|
||||
|
||||
@@ -113,6 +113,9 @@
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-divider>{{ i18n "pages.setting.completeTemplate"}}</a-divider>
|
||||
<a-space direction="horizontal" style="padding: 0 20px">
|
||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.setting.resetDefaultConfig" }}</a-button>
|
||||
</a-space>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.setting.xrayConfigTemplate"}}' desc='{{ i18n "pages.setting.xrayConfigTemplateDesc"}}' v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
@@ -201,7 +204,16 @@
|
||||
await PromiseUtil.sleep(5000);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
async resetXrayConfigToDefault() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.get("/xui/setting/getDefaultJsonConfig");
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.templateSettings = JSON.parse(JSON.stringify(msg.obj, null, 2));
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.getAllSetting();
|
||||
|
||||
@@ -792,12 +792,14 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
||||
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
logger.Warning(err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Warning(err)
|
||||
return nil, err
|
||||
}
|
||||
return traffics[0], err
|
||||
if len(traffics) > 0 {
|
||||
return traffics[0], nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||
@@ -851,6 +853,8 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
||||
|
||||
func (s *InboundService) MigrationRequirements() {
|
||||
db := database.GetDB()
|
||||
|
||||
// Fix inbounds based problems
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
@@ -861,6 +865,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
// Fix Clinet configuration problems
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
@@ -886,6 +891,8 @@ func (s *InboundService) MigrationRequirements() {
|
||||
|
||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||
}
|
||||
|
||||
// Add client traffic row for all clients which has email
|
||||
modelClients, err := s.getClients(inbounds[inbound_index])
|
||||
if err != nil {
|
||||
return
|
||||
@@ -901,4 +908,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
}
|
||||
}
|
||||
db.Save(inbounds)
|
||||
|
||||
// Remove orphaned traffics
|
||||
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -326,3 +327,12 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
}
|
||||
return common.Combine(errs...)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||
var jsonData interface{}
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
@@ -102,80 +102,89 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
address := s.address
|
||||
if inbound.Protocol != model.VMess {
|
||||
return ""
|
||||
}
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": email,
|
||||
"add": s.address,
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
network, _ := stream["network"].(string)
|
||||
typeStr := "none"
|
||||
host := ""
|
||||
path := ""
|
||||
sni := ""
|
||||
fp := ""
|
||||
var alpn []string
|
||||
allowInsecure := false
|
||||
obj["net"] = network
|
||||
switch network {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
typeStr, _ := header["type"].(string)
|
||||
obj["type"] = typeStr
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
path = requestPath[0].(string)
|
||||
obj["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
host = searchHost(headers)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
path, _ = kcp["seed"].(string)
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["path"], _ = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
path = ws["path"].(string)
|
||||
obj["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
host = searchHost(headers)
|
||||
obj["host"] = searchHost(headers)
|
||||
case "http":
|
||||
network = "h2"
|
||||
obj["net"] = "h2"
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
path, _ = http["path"].(string)
|
||||
host = searchHost(http)
|
||||
obj["path"], _ = http["path"].(string)
|
||||
obj["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
header := quic["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
host, _ = quic["security"].(string)
|
||||
path, _ = quic["key"].(string)
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["host"], _ = quic["security"].(string)
|
||||
obj["path"], _ = quic["key"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
path = grpc["serviceName"].(string)
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
obj["type"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
obj["tls"] = security
|
||||
if security == "tls" {
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
if len(alpns) > 0 {
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
}
|
||||
obj["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||
if tlsSetting != nil {
|
||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||
sni, _ = sniValue.(string)
|
||||
obj["sni"], _ = sniValue.(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||
fp, _ = fpValue.(string)
|
||||
obj["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||
allowInsecure, _ = insecure.(bool)
|
||||
obj["allowInsecure"], _ = insecure.(bool)
|
||||
}
|
||||
}
|
||||
serverName, _ := tlsSetting["serverName"].(string)
|
||||
if serverName != "" {
|
||||
address = serverName
|
||||
obj["add"] = serverName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,24 +196,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
obj["id"] = clients[clientIndex].ID
|
||||
obj["aid"] = clients[clientIndex].AlterIds
|
||||
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": email,
|
||||
"add": address,
|
||||
"port": inbound.Port,
|
||||
"id": clients[clientIndex].ID,
|
||||
"aid": clients[clientIndex].AlterIds,
|
||||
"net": network,
|
||||
"type": typeStr,
|
||||
"host": host,
|
||||
"path": path,
|
||||
"tls": security,
|
||||
"sni": sni,
|
||||
"fp": fp,
|
||||
"alpn": strings.Join(alpn, ","),
|
||||
"allowInsecure": allowInsecure,
|
||||
}
|
||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||
}
|
||||
@@ -266,6 +260,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -415,6 +412,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "Save"
|
||||
"restartPanel" = "Restart Panel"
|
||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please go to the server to view the panel log information"
|
||||
"resetDefaultConfig" = "Reset to default config"
|
||||
"panelConfig" = "Panel Configuration"
|
||||
"userSetting" = "User Setting"
|
||||
"xrayConfiguration" = "xray Configuration"
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "ذخیره"
|
||||
"restartPanel" = "ریستارت پنل"
|
||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||
"resetDefaultConfig" = "برگشت به تنظیمات پیشفرض"
|
||||
"panelConfig" = "تنظیمات پنل"
|
||||
"userSetting" = "تنظیمات مدیر"
|
||||
"xrayConfiguration" = "تنظیمات Xray"
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
"save" = "保存配置"
|
||||
"restartPanel" = "重启面板"
|
||||
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息"
|
||||
"resetDefaultConfig" = "重置为默认配置"
|
||||
"panelConfig" = "面板配置"
|
||||
"userSetting" = "用户设置"
|
||||
"xrayConfiguration" = "xray 相关设置"
|
||||
|
||||
@@ -33,9 +33,6 @@ import (
|
||||
//go:embed assets/*
|
||||
var assetsFS embed.FS
|
||||
|
||||
//go:embed assets/favicon.ico
|
||||
var favicon []byte
|
||||
|
||||
//go:embed html/*
|
||||
var htmlFS embed.FS
|
||||
|
||||
@@ -161,11 +158,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
|
||||
engine := gin.Default()
|
||||
|
||||
// Add favicon
|
||||
engine.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Data(200, "image/x-icon", favicon)
|
||||
})
|
||||
|
||||
secret, err := s.settingService.GetSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user