Compare commits

...

12 Commits
1.5.2 ... 1.5.3

Author SHA1 Message Date
Alireza Ahmadi
9e3c7b1db6 v1.5.3 2023-08-08 19:58:30 +02:00
Alireza Ahmadi
ab6c6c0ca6 [bash] separate cloudflare/local acme routines 2023-08-08 19:31:25 +02:00
Alireza Ahmadi
9c0890dd9b [ss] fix 2022 links 2023-08-08 19:14:19 +02:00
Alireza Ahmadi
eee0503200 add system info to main page 2023-08-06 19:10:39 +02:00
Alireza Ahmadi
f3c539dd73 Merge pull request #465 from alireza0/dependabot/go_modules/golang.org/x/text-0.12.0
Bump golang.org/x/text from 0.11.0 to 0.12.0
2023-08-05 19:04:57 +02:00
Alireza Ahmadi
c0464f1d97 multi user HTTP & Socks inbounds 2023-08-05 17:43:28 +02:00
Alireza Ahmadi
b87474d70c [front] better info modal 2023-08-05 17:42:38 +02:00
Alireza Ahmadi
063309dbb7 [front] better table design 2023-08-05 17:41:20 +02:00
dependabot[bot]
294b680972 Bump golang.org/x/text from 0.11.0 to 0.12.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 21:12:42 +00:00
Alireza Ahmadi
f55478422b [install] stop service after download #464 2023-08-03 21:11:24 +02:00
Alireza Ahmadi
619e0c69cd Merge pull request #463 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v3-3.23.7
Bump github.com/shirou/gopsutil/v3 from 3.23.6 to 3.23.7
2023-08-02 15:19:05 +02:00
dependabot[bot]
da5dc3a04f Bump github.com/shirou/gopsutil/v3 from 3.23.6 to 3.23.7
Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.6 to 3.23.7.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.6...v3.23.7)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 21:24:53 +00:00
13 changed files with 340 additions and 107 deletions

View File

@@ -1 +1 @@
1.5.2
1.5.3

6
go.mod
View File

@@ -12,10 +12,10 @@ require (
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.0.9
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.23.6
github.com/shirou/gopsutil/v3 v3.23.7
github.com/xtls/xray-core v1.8.3
go.uber.org/atomic v1.11.0
golang.org/x/text v0.11.0
golang.org/x/text v0.12.0
google.golang.org/grpc v1.57.0
gorm.io/driver/sqlite v1.5.2
gorm.io/gorm v1.25.2
@@ -80,7 +80,7 @@ require (
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect

12
go.sum
View File

@@ -220,8 +220,8 @@ github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:eu
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -366,8 +366,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -376,8 +376,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=

View File

@@ -113,7 +113,6 @@ config_after_install() {
}
install_x-ui() {
systemctl stop x-ui
cd /usr/local/
if [ $# == 0 ]; then
@@ -140,6 +139,7 @@ install_x-ui() {
fi
if [[ -e /usr/local/x-ui/ ]]; then
systemctl stop x-ui
rm /usr/local/x-ui/ -rf
fi

View File

@@ -1279,8 +1279,8 @@ class Inbound extends XrayCommonClass {
}
let password = new Array();
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
if (this.isSS2022) password.push(settings.password);
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
const url = new URL(link);

View File

@@ -1,22 +1,19 @@
{{define "form/http"}}
<a-form layout="inline">
<table width="100%" class="ant-table-tbody">
<tr>
<td>{{ i18n "username"}}</td>
<td>
<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-item>
<a-row>
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
</a-row>
<a-input-group v-for="(account, index) in inbound.settings.accounts">
<a-input style="width: 45%" v-model.trim="account.user"
addon-before='{{ i18n "username" }}'></a-input>
<a-input style="width: 55%" v-model.trim="account.pass"
addon-before='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</a-form-item>
</a-form>
{{end}}

View File

@@ -10,24 +10,25 @@
</a-form-item>
</td>
</tr>
<template v-if="inbound.settings.auth === 'password'">
<tr>
<td>{{ i18n "username" }}</td>
<td>
<tr v-if="inbound.settings.auth === 'password'">
<td colspan="2">
<a-form-item>
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
<a-row>
<a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
</a-row>
<a-input-group v-for="(account, index) in inbound.settings.accounts">
<a-input style="width: 45%" v-model.trim="account.user"
addon-before='{{ i18n "username" }}'></a-input>
<a-input style="width: 55%" v-model.trim="account.pass"
addon-before='{{ i18n "password" }}'>
<template slot="addonAfter">
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
</template>
</a-input>
</a-input-group>
</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>
<tr>
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
<td>
@@ -36,10 +37,10 @@
</a-form-item>
</td>
</tr>
<tr>
<tr v-if="inbound.settings.udp">
<td>IP</td>
<td>
<a-form-item v-if="inbound.settings.udp">
<a-form-item>
<a-input v-model.trim="inbound.settings.ip"></a-input>
</a-form-item>
</td>

View File

@@ -45,17 +45,28 @@
</template>
</table>
</td></tr>
<tr colspan="2" v-if="dbInbound.hasLink()">
<td v-if="inbound.tls">
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td>
<td v-else-if="inbound.reality">
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
</td>
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
<tr colspan="2" v-if="dbInbound.hasLink()">
<td v-if="inbound.tls">
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
</td>
<td v-else-if="inbound.reality">
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
</td>
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
</tr>
</table>
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
<tr>
<td>{{ i18n "encryption" }}</td>
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
</tr><tr v-if="inbound.isSS2022">
<td>{{ i18n "password" }}</td>
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
</tr><tr>
<td>{{ i18n "pages.inbounds.network" }}</td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
</tr>
</table>
<template v-if="infoModal.clientSettings">
@@ -163,19 +174,7 @@
</template>
</template>
<template v-else>
<a-divider></a-divider>
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
<tr>
<th>{{ i18n "encryption" }}</th>
<th>{{ i18n "password" }}</th>
<th>{{ i18n "pages.inbounds.network" }}</th>
</tr><tr>
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
</tr>
</table>
<template v-if="inbound.protocol == Protocols.SHADOWSOCKS && !inbound.isSSMultiUser">
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
<a-divider>URL</a-divider>
<a-row v-for="(link,index) in infoModal.links">
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
@@ -201,17 +200,19 @@
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
</tr>
</table>
</table>
<table v-if="inbound.protocol == Protocols.SOCKS" style="margin-bottom: 10px; width: 100%;">
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
<tr>
<th>{{ i18n "password" }} Auth</th>
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
<th>IP</th>
</tr><tr>
</tr>
<tr>
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
</tr><tr v-if="inbound.settings.auth == 'password'">
</tr>
<template v-if="inbound.settings.auth == 'password'">
<tr>
<td> </td>
<td>{{ i18n "username" }}</td>
<td>{{ i18n "password" }}</td>
@@ -220,9 +221,9 @@
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr>
</template>
</table>
</table>
<table v-if="inbound.protocol == Protocols.HTTP" style="margin-bottom: 10px; width: 100%;">
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
<tr>
<th> </th>
<th>{{ i18n "username" }}</th>
@@ -233,7 +234,6 @@
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
</tr>
</table>
</table>
</template>
</a-modal>
<script>

View File

@@ -11,6 +11,9 @@
.ant-col-sm-24 {
margin-top: 10px;
}
tr.hideExpandIcon .ant-table-row-expand-icon {
display: none;
}
</style>
<body>
@@ -121,8 +124,12 @@
</a-radio-group>
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
:data-source="searchedInbounds"
:loading="spinning" :scroll="{ x: 1300 }"
:loading="spinning" :scroll="{ x: 1200 }"
:pagination="false"
:expand-icon-as-cell="false"
:expand-row-by-click="false"
:expand-icon-column-index="0"
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
style="margin-top: 20px"
@change="() => getDBInbounds()">
<template slot="action" slot-scope="text, dbInbound">
@@ -137,7 +144,7 @@
<a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }}
</a-menu-item>
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.toInbound().isSSMultiUser">
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
<a-menu-item key="addClient">
<a-icon type="user-add"></a-icon>
{{ i18n "pages.client.add"}}
@@ -254,6 +261,7 @@
:columns="innerColumns"
:data-source="getInboundClients(record)"
:pagination="false"
style="margin-left: 20px;"
>
{{template "client_table"}}
</a-table>
@@ -263,6 +271,7 @@
:columns="innerTrojanColumns"
:data-source="getInboundClients(record)"
:pagination="false"
style="margin-left: 20px;"
>
{{template "client_table"}}
</a-table>
@@ -278,6 +287,11 @@
{{template "component/themeSwitcher" .}}
<script>
const columns = [{
title: "ID",
align: 'right',
dataIndex: "id",
width: 30,
}, {
title: '{{ i18n "pages.inbounds.operate" }}',
align: 'center',
width: 30,
@@ -287,11 +301,6 @@
align: 'center',
width: 30,
scopedSlots: { customRender: 'enable' },
}, {
title: "ID",
align: 'center',
dataIndex: "id",
width: 20,
}, {
title: '{{ i18n "pages.inbounds.remark" }}',
align: 'center',
@@ -328,8 +337,8 @@
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 60, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
{ title: 'UUID', width: 120, dataIndex: "id" },
];
@@ -338,8 +347,8 @@
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
{ title: 'Password', width: 120, dataIndex: "password" },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 55, scopedSlots: { customRender: 'expiryTime' } },
{ title: '{{ i18n "password" }}', width: 165, dataIndex: "password" },
];
const app = new Vue({

View File

@@ -42,7 +42,7 @@
:stroke-color="status.cpu.color"
:class="themeSwitcher.darkCardClass"
:percent="status.cpu.percent"></a-progress>
<div>CPU</div>
<div>CPU ([[ status.cpuCount ]]core)</div>
</a-col>
<a-col :span="12" style="text-align: center">
<a-progress type="dashboard" status="normal"
@@ -92,13 +92,16 @@
<a-col :sm="24" :md="12">
<a-card hoverable :class="themeSwitcher.darkCardClass">
{{ i18n "pages.index.operationHours" }}:
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
xray
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
os
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.operationHoursDesc" }}
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
@@ -131,7 +134,36 @@
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="themeSwitcher.darkCardClass">
tcp / udp {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
{{ i18n "usage"}}:
Memory [[ sizeFormat(status.appStats.mem) ]] -
Threads [[ status.appStats.threads ]]
</a-tooltip>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="themeSwitcher.darkCardClass">
Host: [[ status.hostInfo.hostname ]] -
<template v-if="status.hostInfo.ipv4">IPv4:
<a-tooltip>
<template slot="title">
[[ status.hostInfo.ipv4 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</template>
<template v-if="status.hostInfo.ipv6">IPv6:
<a-tooltip>
<template slot="title">
[[ status.hostInfo.ipv6 ]]
</template>
<a-icon type="question-circle" theme="filled"></a-icon>
</a-tooltip>
</template>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-card hoverable :class="themeSwitcher.darkCardClass">
{{ i18n "pages.index.connectionCount" }}: tcp: [[ status.tcpCount ]] udp: [[ status.udpCount ]]
<a-tooltip>
<template slot="title">
{{ i18n "pages.index.connectionCountDesc" }}
@@ -315,6 +347,7 @@
class Status {
constructor(data) {
this.cpu = new CurTotal(0, 0);
this.cpuCount = 0;
this.disk = new CurTotal(0, 0);
this.loads = [0, 0, 0];
this.mem = new CurTotal(0, 0);
@@ -324,12 +357,16 @@
this.tcpCount = 0;
this.udpCount = 0;
this.uptime = 0;
this.appUptime = 0;
this.appStats = {threads: 0, mem: 0, uptime: 0};
this.hostInfo = {hostname:"", ipv4: "", ipv6: ""};
this.xray = {state: State.Stop, errorMsg: "", version: "", color: ""};
if (data == null) {
return;
}
this.cpu = new CurTotal(data.cpu, 100);
this.cpuCount = data.cpuCount;
this.disk = new CurTotal(data.disk.current, data.disk.total);
this.loads = data.loads.map(load => toFixed(load, 2));
this.mem = new CurTotal(data.mem.current, data.mem.total);
@@ -339,6 +376,9 @@
this.tcpCount = data.tcpCount;
this.udpCount = data.udpCount;
this.uptime = data.uptime;
this.appUptime = data.appUptime;
this.appStats = data.appStats;
this.hostInfo = data.hostInfo;
this.xray = data.xray;
switch (this.xray.state) {
case State.Running:

View File

@@ -39,9 +39,10 @@ const (
)
type Status struct {
T time.Time `json:"-"`
Cpu float64 `json:"cpu"`
Mem struct {
T time.Time `json:"-"`
Cpu float64 `json:"cpu"`
CpuCount int `json:"cpuCount"`
Mem struct {
Current uint64 `json:"current"`
Total uint64 `json:"total"`
} `json:"mem"`
@@ -70,6 +71,16 @@ type Status struct {
Sent uint64 `json:"sent"`
Recv uint64 `json:"recv"`
} `json:"netTraffic"`
AppStats struct {
Threads uint32 `json:"threads"`
Mem uint64 `json:"mem"`
Uptime uint64 `json:"uptime"`
} `json:"appStats"`
HostInfo struct {
HostName string `json:"hostname"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
} `json:"hostInfo"`
}
type Release struct {
@@ -176,6 +187,36 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
status.Xray.Version = s.xrayService.GetXrayVersion()
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
status.AppStats.Mem = rtm.Sys
status.AppStats.Threads = uint32(runtime.NumGoroutine())
status.CpuCount = runtime.NumCPU()
if p.IsRunning() {
status.AppStats.Uptime = p.GetUptime()
} else {
status.AppStats.Uptime = 0
}
status.HostInfo.HostName, _ = os.Hostname()
// get ip address
netInterfaces, _ := net.Interfaces()
for i := 0; i < len(netInterfaces); i++ {
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
addrs := netInterfaces[i].Addrs
for _, address := range addrs {
if strings.Contains(address.Addr, ".") {
status.HostInfo.Ipv4 += address.Addr + " "
} else if address.Addr[0:6] != "fe80::" {
status.HostInfo.Ipv6 += address.Addr + " "
}
}
}
}
return status
}

154
x-ui.sh
View File

@@ -403,7 +403,139 @@ show_xray_status() {
fi
}
install_acme() {
cd ~
LOGI "install acme..."
curl https://get.acme.sh | sh
if [ $? -ne 0 ]; then
LOGE "install acme failed"
return 1
else
LOGI "install acme succeed"
fi
return 0
}
ssl_cert_issue_main() {
echo -e "${green}\t1.${plain} Get SSL"
echo -e "${green}\t2.${plain} Revoke"
echo -e "${green}\t3.${plain} Force Renew"
read -p "Choose an option: " choice
case "$choice" in
1) ssl_cert_issue ;;
2)
local domain=""
read -p "Please enter your domain name to revoke the certificate: " domain
~/.acme.sh/acme.sh --revoke -d ${domain}
LOGI "Certificate revoked"
;;
3)
local domain=""
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
*) echo "Invalid choice" ;;
esac
}
ssl_cert_issue() {
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
install_acme
if [ $? -ne 0 ]; then
LOGE "install acme failed, please check logs"
exit 1
fi
fi
# install socat second
case "${release}" in
ubuntu|debian)
apt update && apt install socat -y ;;
centos)
yum -y update && yum -y install socat ;;
fedora)
dnf -y update && dnf -y install socat ;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1 ;;
esac
if [ $? -ne 0 ]; then
LOGE "install socat failed, please check logs"
exit 1
else
LOGI "install socat succeed..."
fi
# get the domain here,and we need verify it
local domain=""
read -p "Please enter your domain name:" domain
LOGD "your domain is:${domain},check it..."
# here we need to judge whether there exists cert already
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
if [ ${currentCert} == ${domain} ]; then
local certInfo=$(~/.acme.sh/acme.sh --list)
LOGE "system already has certs here,can not issue again,current certs details:"
LOGI "$certInfo"
exit 1
else
LOGI "your domain is ready for issuing cert now..."
fi
# create a directory for install cert
certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then
mkdir -p "$certPath"
else
rm -rf "$certPath"
mkdir -p "$certPath"
fi
# get needed port here
local WebPort=80
read -p "please choose which port do you use,default will be 80 port:" WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
LOGE "your input ${WebPort} is invalid,will use default port"
fi
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
# NOTE:This should be handled by user
# open the port and kill the occupied progress
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
if [ $? -ne 0 ]; then
LOGE "issue certs failed,please check logs"
rm -rf ~/.acme.sh/${domain}
exit 1
else
LOGE "issue certs succeed,installing certs..."
fi
# install cert
~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem
if [ $? -ne 0 ]; then
LOGE "install certs failed,exit"
rm -rf ~/.acme.sh/${domain}
exit 1
else
LOGI "install certs succeed,enable auto renew..."
fi
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ]; then
LOGE "auto renew failed, certs details:"
ls -lah cert/*
chmod 755 $certPath/*
exit 1
else
LOGI "auto renew succeed, certs details:"
ls -lah cert/*
chmod 755 $certPath/*
fi
}
ssl_cert_issue_CF() {
echo -E ""
LOGD "******Instructions for use******"
LOGI "This Acme script requires the following data:"
@@ -413,12 +545,14 @@ ssl_cert_issue() {
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
confirm "Confirmed?[y/n]" "y"
if [ $? -eq 0 ]; then
cd ~
LOGI "Install Acme-Script"
curl https://get.acme.sh | sh
if [ $? -ne 0 ]; then
LOGE "Failed to install acme script"
exit 1
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
install_acme
if [ $? -ne 0 ]; then
LOGE "install acme failed, please check logs"
exit 1
fi
fi
CF_Domain=""
CF_GlobalKey=""
@@ -520,7 +654,8 @@ show_menu() {
${green}14.${plain} Cancel x-ui Autostart
————————————————
${green}15.${plain} 一A key installation bbr (latest kernel)
${green}16.${plain}Apply for a SSL certificate with one click(acme script)
${green}16.${plain} 一SSL Certificate Management
${green}17.${plain} 一Cloudflare SSL Certificate
"
show_status
echo && read -p "Please enter your selection [0-16]: " num
@@ -575,7 +710,10 @@ show_menu() {
install_bbr
;;
16)
ssl_cert_issue
ssl_cert_issue_main
;;
17)
ssl_cert_issue_CF
;;
*)
LOGE "Please enter the correct number [0-16]"

View File

@@ -12,6 +12,7 @@ import (
"runtime"
"strings"
"syscall"
"time"
"x-ui/config"
"x-ui/util/common"
@@ -58,16 +59,18 @@ type process struct {
version string
apiPort int
config *Config
lines *queue.Queue
exitErr error
config *Config
lines *queue.Queue
exitErr error
startTime time.Time
}
func newProcess(config *Config) *process {
return &process{
version: "Unknown",
config: config,
lines: queue.New(100),
version: "Unknown",
config: config,
lines: queue.New(100),
startTime: time.Now(),
}
}
@@ -111,6 +114,10 @@ func (p *Process) GetConfig() *Config {
return p.config
}
func (p *Process) GetUptime() uint64 {
return uint64(time.Since(p.startTime).Seconds())
}
func (p *process) refreshAPIPort() {
for _, inbound := range p.config.InboundConfigs {
if inbound.Tag == "api" {