Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d67dff5a4c | ||
|
|
e91daabb18 | ||
|
|
5cc4cf02ee | ||
|
|
1444a00630 | ||
|
|
ae4ea3e091 |
@@ -1,5 +1,5 @@
|
|||||||
# x-ui
|
# x-ui
|
||||||
支持多协议多用户 xray 面板
|
支持多协议多用户的 xray 面板
|
||||||
|
|
||||||
# 功能介绍
|
# 功能介绍
|
||||||
- 系统状态监控
|
- 系统状态监控
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# 安装&升级
|
# 安装&升级
|
||||||
## 测试版
|
## 测试版
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/sprov065/x-ui/master/install.sh) 0.0.1
|
bash <(curl -Ls https://raw.githubusercontent.com/sprov065/x-ui/master/install.sh) 0.1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## 建议系统
|
## 建议系统
|
||||||
@@ -28,6 +28,9 @@ x-ui 相当于 v2-ui 的加强版,未来会加入更多功能,待 x-ui 功
|
|||||||
|
|
||||||
x-ui 可与 v2-ui 并存,数据不互通,不影响对方的运行
|
x-ui 可与 v2-ui 并存,数据不互通,不影响对方的运行
|
||||||
|
|
||||||
|
## 从 v2-ui 迁移
|
||||||
|
将提供便捷的一键迁移方式,正在开发中
|
||||||
|
|
||||||
# Telegram
|
# Telegram
|
||||||
群组:https://t.me/sprov_blog
|
群组:https://t.me/sprov_blog
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.0.2
|
0.1.0
|
||||||
5
main.go
5
main.go
@@ -55,7 +55,10 @@ func runWebServer() {
|
|||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
if sig == syscall.SIGHUP {
|
if sig == syscall.SIGHUP {
|
||||||
server.Stop()
|
err := server.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop server err:", err)
|
||||||
|
}
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
|
|||||||
@@ -54,6 +54,38 @@ class DBInbound {
|
|||||||
this.total = toFixed(gb * ONE_GB, 0);
|
this.total = toFixed(gb * ONE_GB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isVMess() {
|
||||||
|
return this.protocol === Protocols.VMESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isVLess() {
|
||||||
|
return this.protocol === Protocols.VLESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTrojan() {
|
||||||
|
return this.protocol === Protocols.TROJAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSS() {
|
||||||
|
return this.protocol === Protocols.SHADOWSOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSocks() {
|
||||||
|
return this.protocol === Protocols.SOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isHTTP() {
|
||||||
|
return this.protocol === Protocols.HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
|
get address() {
|
||||||
|
let address = location.hostname;
|
||||||
|
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
|
||||||
|
address = this.listen;
|
||||||
|
}
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
toInbound() {
|
toInbound() {
|
||||||
let settings = {};
|
let settings = {};
|
||||||
if (!ObjectUtil.isEmpty(this.settings)) {
|
if (!ObjectUtil.isEmpty(this.settings)) {
|
||||||
@@ -93,9 +125,9 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address = "") {
|
genLink() {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(address, this.remark);
|
return inbound.genLink(this.address, this.remark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -643,6 +643,81 @@ class Inbound extends XrayCommonClass {
|
|||||||
this.stream.network = network;
|
this.stream.network = network;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
get flow() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VLESS:
|
||||||
|
return this.settings.vlesses[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.clients[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) {
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.settings.method;
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get serverName() {
|
||||||
|
if (this.stream.isTls || this.stream.isXTls) {
|
||||||
|
return this.stream.tls.server;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
@@ -785,7 +860,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
if (this.isXTls) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
} else {
|
} else {
|
||||||
params.set("security", this.stream.security);
|
params.set("security", this.stream.security);
|
||||||
@@ -841,7 +916,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isXTls) {
|
if (this.xtls) {
|
||||||
params.set("flow", this.settings.vlesses[0].flow);
|
params.set("flow", this.settings.vlesses[0].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ func (a *InboundController) startTask() {
|
|||||||
webServer := global.GetWebServer()
|
webServer := global.GetWebServer()
|
||||||
c := webServer.GetCron()
|
c := webServer.GetCron()
|
||||||
c.AddFunc("@every 10s", func() {
|
c.AddFunc("@every 10s", func() {
|
||||||
if a.xrayService.IsNeedRestart() {
|
if a.xrayService.IsNeedRestartAndSetFalse() {
|
||||||
a.xrayService.SetIsNeedRestart(false)
|
|
||||||
err := a.xrayService.RestartXray()
|
err := a.xrayService.RestartXray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("restart xray failed:", err)
|
logger.Error("restart xray failed:", err)
|
||||||
@@ -70,7 +69,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.AddInbound(inbound)
|
err = a.inboundService.AddInbound(inbound)
|
||||||
jsonMsg(c, "添加", err)
|
jsonMsg(c, "添加", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetIsNeedRestart(true)
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.DelInbound(id)
|
err = a.inboundService.DelInbound(id)
|
||||||
jsonMsg(c, "删除", err)
|
jsonMsg(c, "删除", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetIsNeedRestart(true)
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +103,6 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
err = a.inboundService.UpdateInbound(inbound)
|
err = a.inboundService.UpdateInbound(inbound)
|
||||||
jsonMsg(c, "修改", err)
|
jsonMsg(c, "修改", err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.xrayService.SetIsNeedRestart(true)
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
web/html/xui/component/inbound_info.html
Normal file
71
web/html/xui/component/inbound_info.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{{define "inboundInfoStream"}}
|
||||||
|
<p>传输: <a-tag color="green">[[ inbound.network ]]</a-tag></p>
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
|
||||||
|
<template v-if="inbound.tls || inbound.xtls">
|
||||||
|
<p v-if="inbound.tls">tls: <a-tag color="green">开启</a-tag></p>
|
||||||
|
<p v-if="inbound.xtls">xtls: <a-tag color="green">开启</a-tag></p>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<p>tls: <a-tag color="red">关闭</a-tag></p>
|
||||||
|
</template>
|
||||||
|
<p v-if="inbound.tls">
|
||||||
|
tls域名: <a-tag color="green">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
||||||
|
</p>
|
||||||
|
<p v-if="inbound.xtls">
|
||||||
|
xtls域名: <a-tag color="green">[[ inbound.serverName ? inbound.serverName : "无" ]]</a-tag>
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
|
{{define "component/inboundInfoComponent"}}
|
||||||
|
<div>
|
||||||
|
<p>协议: <a-tag color="green">[[ dbInbound.protocol ]]</a-tag></p>
|
||||||
|
<p>地址: <a-tag color="blue">[[ dbInbound.address ]]</a-tag></p>
|
||||||
|
<p>端口: <a-tag color="green">[[ dbInbound.port ]]</a-tag></p>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isVMess">
|
||||||
|
<p>uuid: <a-tag color="green">[[ inbound.uuid ]]</a-tag></p>
|
||||||
|
<p>alterId: <a-tag color="green">[[ inbound.alterId ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isVLess">
|
||||||
|
<p>uuid: <a-tag color="green">[[ inbound.uuid ]]</a-tag></p>
|
||||||
|
<p v-if="inbound.isXTls">flow: <a-tag color="green">[[ inbound.flow ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isTrojan">
|
||||||
|
<p>密码: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isSS">
|
||||||
|
<p>加密: <a-tag color="green">[[ inbound.method ]]</a-tag></p>
|
||||||
|
<p>密码: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isSocks">
|
||||||
|
<p>用户名: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
|
||||||
|
<p>密码: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isHTTP">
|
||||||
|
<p>用户名: <a-tag color="green">[[ inbound.username ]]</a-tag></p>
|
||||||
|
<p>密码: <a-tag color="green">[[ inbound.password ]]</a-tag></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
{{template "inboundInfoStream"}}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "component/inboundInfo"}}
|
||||||
|
<script>
|
||||||
|
Vue.component('inbound-info', {
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
props: ["dbInbound", "inbound"],
|
||||||
|
template: `{{template "component/inboundInfoComponent"}}`,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<a-form-item label="id">
|
<a-form-item label="id">
|
||||||
<a-input v-model.trim="inbound.settings.vlesses[0].id"></a-input>
|
<a-input v-model.trim="inbound.settings.vlesses[0].id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="flow">
|
<a-form-item v-if="inbound.xtls" label="flow">
|
||||||
<a-select v-model="inbound.settings.vlesses[0].flow" style="width: 150px">
|
<a-select v-model="inbound.settings.vlesses[0].flow" style="width: 150px">
|
||||||
<a-select-option value="">无</a-select-option>
|
<a-select-option value="">无</a-select-option>
|
||||||
<a-select-option v-for="key in VLESS_FLOW" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in VLESS_FLOW" :value="key">[[ key ]]</a-select-option>
|
||||||
|
|||||||
42
web/html/xui/inbound_info_modal.html
Normal file
42
web/html/xui/inbound_info_modal.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{{define "inboundInfoModal"}}
|
||||||
|
{{template "component/inboundInfo"}}
|
||||||
|
<a-modal id="inbound-info-modal" v-model="infoModal.visible" title="详细信息" @ok="infoModal.ok"
|
||||||
|
:closable="true" :mask-closable="true"
|
||||||
|
ok-text="复制链接" cancel-text='{{ i18n "close" }}'>
|
||||||
|
<inbound-info :db-inbound="dbInbound" :inbound="inbound"></inbound-info>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const infoModal = {
|
||||||
|
visible: false,
|
||||||
|
inbound: new Inbound(),
|
||||||
|
dbInbound: new DBInbound(),
|
||||||
|
ok() {
|
||||||
|
|
||||||
|
},
|
||||||
|
show(dbInbound) {
|
||||||
|
this.inbound = dbInbound.toInbound();
|
||||||
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
infoModal.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#inbound-info-modal',
|
||||||
|
data: {
|
||||||
|
infoModal,
|
||||||
|
get dbInbound() {
|
||||||
|
return this.infoModal.dbInbound;
|
||||||
|
},
|
||||||
|
get inbound() {
|
||||||
|
return this.infoModal.inbound;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -63,10 +63,15 @@
|
|||||||
<a-tag v-else color="cyan">无限制</a-tag>
|
<a-tag v-else color="cyan">无限制</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="settings" slot-scope="text, dbInbound">
|
<template slot="settings" slot-scope="text, dbInbound">
|
||||||
<a-button type="link">查看</a-button>
|
<a-button type="link" @click="showInfo(dbInbound)">查看</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template slot="streamSettings" slot-scope="text, dbInbound">
|
<template slot="stream" slot-scope="text, dbInbound, index">
|
||||||
<a-button type="link">查看</a-button>
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
<a-tag color="green">[[ inbounds[index].stream.network ]]</a-tag>
|
||||||
|
<a-tag v-if="inbounds[index].stream.isTls" color="blue">tls</a-tag>
|
||||||
|
<a-tag v-if="inbounds[index].stream.isXTls" color="blue">xtls</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>无</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound)"></a-switch>
|
||||||
@@ -109,25 +114,25 @@
|
|||||||
}, {
|
}, {
|
||||||
title: "流量↑|↓",
|
title: "流量↑|↓",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 80,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
// }, {
|
}, {
|
||||||
// title: "settings",
|
title: "详细信息",
|
||||||
// align: 'center',
|
align: 'center',
|
||||||
// width: 60,
|
width: 60,
|
||||||
// scopedSlots: { customRender: 'settings' },
|
scopedSlots: { customRender: 'settings' },
|
||||||
// }, {
|
}, {
|
||||||
// title: "streamSettings",
|
title: "传输配置",
|
||||||
// align: 'center',
|
align: 'center',
|
||||||
// width: 60,
|
width: 60,
|
||||||
// scopedSlots: { customRender: 'streamSettings' },
|
scopedSlots: { customRender: 'stream' },
|
||||||
}, {
|
}, {
|
||||||
title: "启用",
|
title: "启用",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'enable' },
|
scopedSlots: { customRender: 'enable' },
|
||||||
}, {
|
}, {
|
||||||
title: "action",
|
title: "操作",
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'action' },
|
scopedSlots: { customRender: 'action' },
|
||||||
@@ -139,6 +144,7 @@
|
|||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
siderDrawer,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
},
|
},
|
||||||
@@ -156,9 +162,12 @@
|
|||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
|
this.inbounds.splice(0);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
this.dbInbounds.push(new DBInbound(inbound));
|
const dbInbound = new DBInbound(inbound);
|
||||||
|
this.inbounds.push(dbInbound.toInbound());
|
||||||
|
this.dbInbounds.push(dbInbound);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
@@ -256,13 +265,12 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound) {
|
showQrcode(dbInbound) {
|
||||||
let address = location.hostname;
|
const link = dbInbound.genLink();
|
||||||
if (!ObjectUtil.isEmpty(dbInbound.listen) && dbInbound.listen !== "0.0.0.0") {
|
|
||||||
address = dbInbound.listen;
|
|
||||||
}
|
|
||||||
const link = dbInbound.genLink(address);
|
|
||||||
qrModal.show('二维码', link);
|
qrModal.show('二维码', link);
|
||||||
},
|
},
|
||||||
|
showInfo(dbInbound) {
|
||||||
|
infoModal.show(dbInbound);
|
||||||
|
},
|
||||||
switchEnable(dbInbound) {
|
switchEnable(dbInbound) {
|
||||||
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
this.submit(`/xui/inbound/update/${dbInbound.id}`, dbInbound);
|
||||||
},
|
},
|
||||||
@@ -301,5 +309,6 @@
|
|||||||
{{template "promptModal"}}
|
{{template "promptModal"}}
|
||||||
{{template "qrcodeModal"}}
|
{{template "qrcodeModal"}}
|
||||||
{{template "textModal"}}
|
{{template "textModal"}}
|
||||||
|
{{template "inboundInfoModal"}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -38,11 +38,11 @@
|
|||||||
<a-tabs default-active-key="1">
|
<a-tabs default-active-key="1">
|
||||||
<a-tab-pane key="1" tab="面板配置">
|
<a-tab-pane key="1" tab="面板配置">
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
<setting-list-item type="text" title="面板监听 IP" desc="默认留空监听所有 IP" v-model="allSetting.webListen"></setting-list-item>
|
<setting-list-item type="text" title="面板监听 IP" desc="默认留空监听所有 IP,重启面板生效" v-model="allSetting.webListen"></setting-list-item>
|
||||||
<setting-list-item type="number" title="面板监听端口" v-model.number="allSetting.webPort"></setting-list-item>
|
<setting-list-item type="number" title="面板监听端口" desc="重启面板生效" v-model.number="allSetting.webPort"></setting-list-item>
|
||||||
<setting-list-item type="text" title="面板证书公钥文件路径" desc="填写一个 '/' 开头的绝对路径" v-model="allSetting.webCertFile"></setting-list-item>
|
<setting-list-item type="text" title="面板证书公钥文件路径" desc="填写一个 '/' 开头的绝对路径,重启面板生效" v-model="allSetting.webCertFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title="面板证书密钥文件路径" desc="填写一个 '/' 开头的绝对路径" v-model="allSetting.webKeyFile"></setting-list-item>
|
<setting-list-item type="text" title="面板证书密钥文件路径" desc="填写一个 '/' 开头的绝对路径,重启面板生效" v-model="allSetting.webKeyFile"></setting-list-item>
|
||||||
<setting-list-item type="text" title="面板 url 根路径" desc="必须以 '/' 开头,以 '/' 结尾" v-model="allSetting.webBasePath"></setting-list-item>
|
<setting-list-item type="text" title="面板 url 根路径" desc="必须以 '/' 开头,以 '/' 结尾,重启面板生效" v-model="allSetting.webBasePath"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="用户设置">
|
<a-tab-pane key="2" tab="用户设置">
|
||||||
@@ -68,12 +68,12 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="3" tab="xray 相关设置">
|
<a-tab-pane key="3" tab="xray 相关设置">
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
<setting-list-item type="textarea" title="xray 配置模版" desc="以该模版为基础生成最终的 xray 配置文件" v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
<setting-list-item type="textarea" title="xray 配置模版" desc="以该模版为基础生成最终的 xray 配置文件,重启面板生效" v-model="allSetting.xrayTemplateConfig"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="4" tab="其他设置">
|
<a-tab-pane key="4" tab="其他设置">
|
||||||
<a-list item-layout="horizontal" style="background: white">
|
<a-list item-layout="horizontal" style="background: white">
|
||||||
<setting-list-item type="text" title="时区" desc="定时任务按照该时区的时间运行" v-model="allSetting.timeLocation"></setting-list-item>
|
<setting-list-item type="text" title="时区" desc="定时任务按照该时区的时间运行,重启面板生效" v-model="allSetting.timeLocation"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) DisableInvalidInbounds() (bool, error) {
|
func (s *InboundService) DisableInvalidInbounds() (int64, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
result := db.Model(model.Inbound{}).
|
result := db.Model(model.Inbound{}).
|
||||||
Where("up + down >= total and total > 0 and enable = ?", true).
|
Where("up + down >= total and total > 0 and enable = ?", true).
|
||||||
Update("enable", false)
|
Update("enable", false)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
count := result.RowsAffected
|
count := result.RowsAffected
|
||||||
return count > 0, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"sync"
|
"sync"
|
||||||
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
var p *xray.Process
|
var p *xray.Process
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
var isNeedXrayRestart atomic.Bool
|
||||||
var result string
|
var result string
|
||||||
|
|
||||||
type XrayService struct {
|
type XrayService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
|
|
||||||
isNeedXrayRestart atomic.Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) IsXrayRunning() bool {
|
func (s *XrayService) IsXrayRunning() bool {
|
||||||
@@ -87,6 +87,7 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, error) {
|
|||||||
func (s *XrayService) RestartXray() error {
|
func (s *XrayService) RestartXray() error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
logger.Debug("restart xray")
|
||||||
|
|
||||||
xrayConfig, err := s.GetXrayConfig()
|
xrayConfig, err := s.GetXrayConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,16 +109,17 @@ func (s *XrayService) RestartXray() error {
|
|||||||
func (s *XrayService) StopXray() error {
|
func (s *XrayService) StopXray() error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
logger.Debug("stop xray")
|
||||||
if s.IsXrayRunning() {
|
if s.IsXrayRunning() {
|
||||||
return p.Stop()
|
return p.Stop()
|
||||||
}
|
}
|
||||||
return errors.New("xray is not running")
|
return errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) SetIsNeedRestart(needRestart bool) {
|
func (s *XrayService) SetToNeedRestart() {
|
||||||
s.isNeedXrayRestart.Store(needRestart)
|
isNeedXrayRestart.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) IsNeedRestart() bool {
|
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
|
||||||
return s.isNeedXrayRestart.Load()
|
return isNeedXrayRestart.CAS(true, false)
|
||||||
}
|
}
|
||||||
|
|||||||
33
web/web.go
33
web/web.go
@@ -44,7 +44,8 @@ func (f *wrapAssetsFS) Open(name string) (fs.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
listener net.Listener
|
httpServer *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
|
||||||
index *controller.IndexController
|
index *controller.IndexController
|
||||||
server *controller.ServerController
|
server *controller.ServerController
|
||||||
@@ -253,7 +254,7 @@ func (s *Server) startTask() {
|
|||||||
if checkTime < 2 {
|
if checkTime < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.xrayService.SetIsNeedRestart(true)
|
s.xrayService.SetToNeedRestart()
|
||||||
})
|
})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -275,13 +276,14 @@ func (s *Server) startTask() {
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 每分钟检查一次 inbound 流量超出情况
|
// 每 30 秒检查一次 inbound 流量超出情况
|
||||||
s.cron.AddFunc("@every 1m", func() {
|
s.cron.AddFunc("@every 30s", func() {
|
||||||
needRestart, err := s.inboundService.DisableInvalidInbounds()
|
count, err := s.inboundService.DisableInvalidInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("disable invalid inbounds err:", err)
|
logger.Warning("disable invalid inbounds err:", err)
|
||||||
} else if needRestart {
|
} else if count > 0 {
|
||||||
s.xrayService.SetIsNeedRestart(true)
|
logger.Debugf("disabled %v inbounds", count)
|
||||||
|
s.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -348,7 +350,11 @@ func (s *Server) Start() (err error) {
|
|||||||
|
|
||||||
s.startTask()
|
s.startTask()
|
||||||
|
|
||||||
go engine.RunListener(listener)
|
s.httpServer = &http.Server{
|
||||||
|
Handler: engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.httpServer.Serve(listener)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -359,10 +365,15 @@ func (s *Server) Stop() error {
|
|||||||
if s.cron != nil {
|
if s.cron != nil {
|
||||||
s.cron.Stop()
|
s.cron.Stop()
|
||||||
}
|
}
|
||||||
if s.listener != nil {
|
var err1 error
|
||||||
return s.listener.Close()
|
var err2 error
|
||||||
|
if s.httpServer != nil {
|
||||||
|
err1 = s.httpServer.Shutdown(s.ctx)
|
||||||
}
|
}
|
||||||
return nil
|
if s.listener != nil {
|
||||||
|
err2 = s.listener.Close()
|
||||||
|
}
|
||||||
|
return common.Combine(err1, err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetCtx() context.Context {
|
func (s *Server) GetCtx() context.Context {
|
||||||
|
|||||||
Reference in New Issue
Block a user