mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-15 21:56:10 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f920bb08c | ||
|
|
3528135297 | ||
|
|
bca0f63239 | ||
|
|
607fdc9f47 | ||
|
|
8775eb70e2 | ||
|
|
59204cdb0c | ||
|
|
49eedb7057 | ||
|
|
312c551cfb | ||
|
|
c5389d86a3 | ||
|
|
adf73cd87a | ||
|
|
a3f4e6f35c | ||
|
|
b8eee6e373 | ||
|
|
b3d3f76e84 | ||
|
|
3be40f8595 |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.6.1
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.6.1
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-s390x.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.6.1
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
@@ -11,7 +11,7 @@ else
|
||||
fi
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.1/Xray-linux-${ARCH}.zip"
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.3/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.5.1
|
||||
1.5.2
|
||||
4
go.mod
4
go.mod
@@ -16,7 +16,7 @@ require (
|
||||
github.com/xtls/xray-core v1.8.3
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.11.0
|
||||
google.golang.org/grpc v1.56.2
|
||||
google.golang.org/grpc v1.57.0
|
||||
gorm.io/driver/sqlite v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
)
|
||||
@@ -83,7 +83,7 @@ require (
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -409,14 +409,14 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
|
||||
@@ -1,25 +1,47 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
var logger *logging.Logger
|
||||
var logBuffer []struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitLogger(logging.INFO)
|
||||
}
|
||||
|
||||
func InitLogger(level logging.Level) {
|
||||
format := logging.MustStringFormatter(
|
||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||
)
|
||||
newLogger := logging.MustGetLogger("x-ui")
|
||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
var err error
|
||||
var backend logging.Backend
|
||||
var format logging.Formatter
|
||||
ppid := os.Getppid()
|
||||
|
||||
if ppid == 1 {
|
||||
backend, err = logging.NewSyslogBackend("")
|
||||
format = logging.MustStringFormatter(
|
||||
`%{level} - %{message}`,
|
||||
)
|
||||
}
|
||||
if err != nil || ppid != 1 {
|
||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||
format = logging.MustStringFormatter(
|
||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||
)
|
||||
}
|
||||
|
||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||
backendLeveled.SetLevel(level, "")
|
||||
backendLeveled.SetLevel(level, "x-ui")
|
||||
newLogger.SetBackend(backendLeveled)
|
||||
|
||||
logger = newLogger
|
||||
@@ -27,32 +49,70 @@ func InitLogger(level logging.Level) {
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
logger.Debug(args...)
|
||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
logger.Debugf(format, args...)
|
||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
logger.Info(args...)
|
||||
addToBuffer("INFO", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
logger.Infof(format, args...)
|
||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Warning(args ...interface{}) {
|
||||
logger.Warning(args...)
|
||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
logger.Warningf(format, args...)
|
||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
logger.Error(args...)
|
||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
logger.Errorf(format, args...)
|
||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func addToBuffer(level string, newLog string) {
|
||||
t := time.Now()
|
||||
if len(logBuffer) >= 10240 {
|
||||
logBuffer = logBuffer[1:]
|
||||
}
|
||||
|
||||
logLevel, _ := logging.LogLevel(level)
|
||||
logBuffer = append(logBuffer, struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}{
|
||||
time: t.Format("2006/01/02 15:04:05"),
|
||||
level: logLevel,
|
||||
log: newLog,
|
||||
})
|
||||
}
|
||||
|
||||
func GetLogs(c int, level string) []string {
|
||||
var output []string
|
||||
logLevel, _ := logging.LogLevel(level)
|
||||
|
||||
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
|
||||
if logBuffer[i].level <= logLevel {
|
||||
output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -657,7 +657,10 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
}
|
||||
}
|
||||
|
||||
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||
if method[0] == '2' {
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
}
|
||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||
url, _ := url.Parse(link)
|
||||
q := url.Query()
|
||||
|
||||
@@ -16,9 +16,10 @@ const VmessMethods = {
|
||||
};
|
||||
|
||||
const SSMethods = {
|
||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||
AES_256_GCM: 'aes-256-gcm',
|
||||
AES_128_GCM: 'aes-128-gcm',
|
||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
@@ -873,7 +874,10 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get isSSMultiUser() {
|
||||
return [SSMethods.BLAKE3_AES_128_GCM,SSMethods.BLAKE3_AES_256_GCM].includes(this.method);
|
||||
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
||||
}
|
||||
get isSS2022(){
|
||||
return this.method.substring(0,4) === "2022";
|
||||
}
|
||||
|
||||
get serverName() {
|
||||
@@ -1274,9 +1278,11 @@ class Inbound extends XrayCommonClass {
|
||||
break;
|
||||
}
|
||||
|
||||
let clientPassword = this.isSSMultiUser ? ':' + settings.shadowsockses[clientIndex].password : '';
|
||||
let password = new Array();
|
||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||
if (this.isSS2022) password.push(settings.password);
|
||||
|
||||
let link = `ss://${safeBase64(settings.method + ':' + settings.password + clientPassword)}@${address}:${this.port}`;
|
||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
||||
const url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
url.searchParams.set(key, value)
|
||||
@@ -1872,8 +1878,9 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||
};
|
||||
|
||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(), totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomText(16,16)) {
|
||||
super();
|
||||
this.method = method;
|
||||
this.password = password;
|
||||
this.email = email;
|
||||
this.totalGB = totalGB;
|
||||
@@ -1885,6 +1892,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
method: this.method,
|
||||
password: this.password,
|
||||
email: this.email,
|
||||
totalGB: this.totalGB,
|
||||
@@ -1897,6 +1905,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||
json.method,
|
||||
json.password,
|
||||
json.email,
|
||||
json.totalGB,
|
||||
|
||||
@@ -118,11 +118,9 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||
|
||||
func (a *ServerController) getLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logs, err := a.serverService.GetLogs(count)
|
||||
if err != nil {
|
||||
jsonMsg(c, "getLogs", err)
|
||||
return
|
||||
}
|
||||
level := c.PostForm("level")
|
||||
syslog := c.PostForm("syslog")
|
||||
logs := a.serverService.GetLogs(count, level, syslog)
|
||||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -210,21 +210,12 @@
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.delayedStart = false;
|
||||
},
|
||||
getClients(protocol, clientSettings) {
|
||||
switch(protocol){
|
||||
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;
|
||||
}
|
||||
},
|
||||
newClient(protocol) {
|
||||
switch (protocol) {
|
||||
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks();
|
||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings.shadowsockses[0].method);
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
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());
|
||||
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method));
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr v-if="inbound.isSS2022">
|
||||
<td>{{ i18n "password" }}
|
||||
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
</td>
|
||||
|
||||
@@ -110,6 +110,15 @@
|
||||
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
||||
}
|
||||
if (["aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305"].includes(this.inModal.inbound.settings.method)) {
|
||||
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||
client.method = this.inModal.inbound.settings.method;
|
||||
})
|
||||
} else {
|
||||
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||
client.method = "";
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (this.inModal.inbound.settings.shadowsockses.length > 0){
|
||||
this.inModal.inbound.settings.shadowsockses = [];
|
||||
|
||||
@@ -20,9 +20,13 @@
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||
<transition name="list" appear>
|
||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||
</a-tag>
|
||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||
message='{{ i18n "secAlertTitle" }}'
|
||||
color="red"
|
||||
description='{{ i18n "secAlertSsl" }}'
|
||||
show-icon closable
|
||||
>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
||||
@@ -366,7 +370,8 @@
|
||||
domain: '',
|
||||
tls: false
|
||||
},
|
||||
tgBotEnable: false
|
||||
tgBotEnable: false,
|
||||
showAlert: false,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning = true) {
|
||||
@@ -952,6 +957,9 @@
|
||||
}, 500)
|
||||
},
|
||||
mounted() {
|
||||
if (window.location.protocol !== "https:") {
|
||||
this.showAlert = true;
|
||||
}
|
||||
this.loading();
|
||||
this.getDefaultSettings();
|
||||
if (this.isRefreshEnabled) {
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||
<transition name="list" appear>
|
||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||
message='{{ i18n "secAlertTitle" }}'
|
||||
color="red"
|
||||
description='{{ i18n "secAlertSsl" }}'
|
||||
show-icon closable
|
||||
>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-row>
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
@@ -110,7 +119,7 @@
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||
{{ i18n "menu.link" }}:
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||
</a-card>
|
||||
@@ -211,7 +220,7 @@
|
||||
<a-form-item label="Count">
|
||||
<a-select v-model="logModal.rows"
|
||||
style="width: 80px"
|
||||
@change="openLogs(logModal.rows)"
|
||||
@change="openLogs()"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
@@ -219,8 +228,22 @@
|
||||
<a-select-option value="100">100</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Log Level">
|
||||
<a-select v-model="logModal.level"
|
||||
style="width: 120px"
|
||||
@change="openLogs()"
|
||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</a-select-option>
|
||||
<a-select-option value="warning">Warning</a-select-option>
|
||||
<a-select-option value="err">Error</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="SysLog">
|
||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
|
||||
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" style="margin-bottom: 10px;"
|
||||
@@ -349,10 +372,11 @@
|
||||
visible: false,
|
||||
logs: '',
|
||||
rows: 20,
|
||||
show(logs, rows) {
|
||||
level: 'info',
|
||||
syslog: false,
|
||||
show(logs) {
|
||||
this.visible = true;
|
||||
this.rows = rows;
|
||||
this.logs = logs.join("\n");
|
||||
this.logs = logs? logs.join("\n"): "No Record...";
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
@@ -394,6 +418,7 @@
|
||||
backupModal,
|
||||
spinning: false,
|
||||
loadingTip: '{{ i18n "loading"}}',
|
||||
showAlert: false,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||
@@ -449,14 +474,14 @@
|
||||
return;
|
||||
}
|
||||
},
|
||||
async openLogs(rows){
|
||||
async openLogs(){
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/logs/'+rows);
|
||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
logModal.show(msg.obj, rows);
|
||||
logModal.show(msg.obj);
|
||||
},
|
||||
async openConfig() {
|
||||
this.loading(true);
|
||||
@@ -512,6 +537,9 @@
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
if (window.location.protocol !== "https:") {
|
||||
this.showAlert = true;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
await this.getStatus();
|
||||
|
||||
@@ -52,6 +52,15 @@
|
||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
||||
<transition name="list" appear>
|
||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||
message='{{ i18n "secAlertTitle" }}'
|
||||
color="red"
|
||||
description='{{ i18n "secAlertSsl" }}'
|
||||
show-icon closable
|
||||
>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<a-space direction="vertical">
|
||||
<a-space direction="horizontal">
|
||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||
@@ -228,19 +237,6 @@
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigGoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4"}}' desc='{{ i18n "pages.settings.templates.xrayConfigNetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.templates.manualLists"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<h2 class="collapse-title">
|
||||
<a-icon type="warning"></a-icon>
|
||||
{{ i18n "pages.settings.templates.manualListsDesc" }}
|
||||
</h2>
|
||||
</a-row>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedIPs"}}' v-model="manualBlockedIPs"></setting-list-item>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualBlockedDomains"}}' v-model="manualBlockedDomains"></setting-list-item>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectIPs"}}' v-model="manualDirectIPs"></setting-list-item>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualDirectDomains"}}' v-model="manualDirectDomains"></setting-list-item>
|
||||
<setting-list-item type="textarea" title='{{ i18n "pages.settings.templates.manualIPv4Domains"}}' v-model="manualIPv4Domains"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.resetDefaultConfig"}}'>
|
||||
<a-space direction="horizontal" style="padding: 0 20px">
|
||||
<a-button type="primary" @click="resetXrayConfigToDefault">{{ i18n "pages.settings.resetDefaultConfig" }}</a-button>
|
||||
@@ -375,6 +371,7 @@
|
||||
saveBtnDisable: true,
|
||||
user: {},
|
||||
lang: getLang(),
|
||||
showAlert: false,
|
||||
ipv4Settings: {
|
||||
tag: "IPv4",
|
||||
protocol: "freedom",
|
||||
@@ -563,6 +560,9 @@
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (window.location.protocol !== "https:") {
|
||||
this.showAlert = true;
|
||||
}
|
||||
await this.getAllSetting();
|
||||
while (true) {
|
||||
await PromiseUtil.sleep(1000);
|
||||
|
||||
@@ -302,21 +302,17 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err1 != nil {
|
||||
logger.Debug("Unable to delete old inbound by api:", err1)
|
||||
needRestart = true
|
||||
} else {
|
||||
if s.xrayApi.DelInbound(tag) == nil {
|
||||
logger.Debug("Old inbound deleted by api:", tag)
|
||||
|
||||
if inbound.Enable {
|
||||
inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ")
|
||||
if err2 != nil {
|
||||
logger.Debug("Unable to marshal updated inbound config:", err2)
|
||||
}
|
||||
|
||||
}
|
||||
if inbound.Enable {
|
||||
inboundJson, err2 := json.MarshalIndent(oldInbound.GenXrayInboundConfig(), "", " ")
|
||||
if err2 != nil {
|
||||
logger.Debug("Unable to marshal updated inbound config:", err2)
|
||||
needRestart = true
|
||||
} else {
|
||||
err2 = s.xrayApi.AddInbound(inboundJson)
|
||||
if err1 == nil {
|
||||
if err2 == nil {
|
||||
logger.Debug("Updated inbound added by api:", oldInbound.Tag)
|
||||
} else {
|
||||
logger.Debug("Unable to update inbound by api:", err2)
|
||||
@@ -447,15 +443,21 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
if len(client.Email) > 0 {
|
||||
s.AddClientStat(tx, data.Id, &client)
|
||||
if client.Enable {
|
||||
cipher := ""
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"flow": client.Flow,
|
||||
"password": client.Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client added by api:", client.Email)
|
||||
} else {
|
||||
logger.Debug("Error in adding client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
@@ -516,15 +518,18 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
return false, err
|
||||
}
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if len(email) > 0 {
|
||||
err = s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err == nil {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client deleted by api:", email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Unable to del client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
return needRestart, db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
@@ -622,26 +627,35 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
}
|
||||
}
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if len(oldEmail) > 0 {
|
||||
s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
if s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) == nil {
|
||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
||||
}
|
||||
if clients[0].Enable {
|
||||
cipher := ""
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
"email": clients[0].Email,
|
||||
"id": clients[0].ID,
|
||||
"flow": clients[0].Flow,
|
||||
"password": clients[0].Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client edited by api:", clients[0].Email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Error in adding client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
} else {
|
||||
logger.Debug("Client disabled by api:", clients[0].Email)
|
||||
needRestart = false
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
} else {
|
||||
logger.Debug("Client old email not found")
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
return needRestart, tx.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
@@ -695,6 +709,11 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e
|
||||
return err
|
||||
}
|
||||
|
||||
// Avoid empty slice error
|
||||
if len(dbClientTraffics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -786,10 +805,11 @@ func (s *InboundService) DisableInvalidInbounds() (bool, int64, error) {
|
||||
}
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, tag := range tags {
|
||||
err = s.xrayApi.DelInbound(tag)
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err == nil {
|
||||
logger.Debug("Inbound disabled by api:", tag)
|
||||
} else {
|
||||
logger.Debug("Error in disabling inbound by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
@@ -825,10 +845,11 @@ func (s *InboundService) DisableInvalidClients() (bool, int64, error) {
|
||||
}
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, result := range results {
|
||||
err = s.xrayApi.RemoveUser(result.Tag, result.Email)
|
||||
if err == nil {
|
||||
err1 := s.xrayApi.RemoveUser(result.Tag, result.Email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client disabled by api:", result.Email)
|
||||
} else {
|
||||
logger.Debug("Error in disabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
@@ -911,15 +932,26 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
cipher := ""
|
||||
if string(inbound.Protocol) == "shadowsocks" {
|
||||
var oldSettings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"flow": client.Flow,
|
||||
"password": client.Password,
|
||||
"cipher": cipher,
|
||||
})
|
||||
if err1 == nil {
|
||||
logger.Debug("Client enabled due to reset traffic:", clientEmail)
|
||||
} else {
|
||||
logger.Debug("Error in enabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/config"
|
||||
@@ -334,27 +335,26 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) GetLogs(count string) ([]string, error) {
|
||||
// Define the journalctl command and its arguments
|
||||
var cmdArgs []string
|
||||
if runtime.GOOS == "linux" {
|
||||
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
||||
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||
c, _ := strconv.Atoi(count)
|
||||
var lines []string
|
||||
|
||||
if syslog == "true" {
|
||||
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
||||
// Run the command
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return []string{"Failed to run journalctl command!"}
|
||||
}
|
||||
lines = strings.Split(out.String(), "\n")
|
||||
} else {
|
||||
return []string{"Unsupported operating system"}, nil
|
||||
lines = logger.GetLogs(c, level)
|
||||
}
|
||||
|
||||
// Run the command
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
|
||||
return lines, nil
|
||||
return lines
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
|
||||
@@ -116,7 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
}
|
||||
for key := range c {
|
||||
if key != "email" && key != "id" && key != "password" && key != "flow" {
|
||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
|
||||
delete(c, key)
|
||||
}
|
||||
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
"clients" = "Clients"
|
||||
"usage" = "Usage"
|
||||
"remained" = "Remained"
|
||||
"secAlertTitle" = "Security Alert"
|
||||
"secAlertSsl" = "This connection is not secure; Please refrain from entering sensitive information until TLS is activated for data protection"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "System Status"
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
"clients" = "کاربران"
|
||||
"usage" = "استفاده"
|
||||
"remained" = "باقیمانده"
|
||||
"secAlertTitle" = "هشدار امنیتی"
|
||||
"secAlertSsl" = "این اتصال امن نیست؛ لطفا تا زمانی که تیالاس برای حفاظت از داده ها فعال نشده است از وارد کردن اطلاعات حساس خودداری کنید"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "وضعیت سیستم"
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
"clients" = "клиенты"
|
||||
"usage" = "использование"
|
||||
"remained" = "остались"
|
||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации, пока TLS не будет активирован для защиты данных"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "статус системы"
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
"clients" = "客户端"
|
||||
"usage" = "用法"
|
||||
"remained" = "仍然存在"
|
||||
"secAlertTitle" = "安全警报"
|
||||
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "系统状态"
|
||||
|
||||
31
xray/api.go
31
xray/api.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||
"github.com/xtls/xray-core/proxy/trojan"
|
||||
"github.com/xtls/xray-core/proxy/vless"
|
||||
"github.com/xtls/xray-core/proxy/vmess"
|
||||
@@ -61,10 +62,12 @@ func (x *XrayAPI) AddInbound(inbound []byte) error {
|
||||
err := json.Unmarshal(inbound, conf)
|
||||
if err != nil {
|
||||
logger.Debug("Failed to unmarshal inbound:", err)
|
||||
return err
|
||||
}
|
||||
config, err := conf.Build()
|
||||
if err != nil {
|
||||
logger.Debug("Failed to build inbound Detur:", err)
|
||||
return err
|
||||
}
|
||||
inboundConfig := command.AddInboundRequest{Inbound: config}
|
||||
|
||||
@@ -98,9 +101,31 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
||||
Password: user["password"].(string),
|
||||
})
|
||||
case "shadowsocks":
|
||||
account = serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user["password"].(string),
|
||||
})
|
||||
var ssCipherType shadowsocks.CipherType
|
||||
switch user["cipher"].(string) {
|
||||
case "aes-128-gcm":
|
||||
ssCipherType = shadowsocks.CipherType_AES_128_GCM
|
||||
case "aes-256-gcm":
|
||||
ssCipherType = shadowsocks.CipherType_AES_256_GCM
|
||||
case "chacha20-poly1305":
|
||||
ssCipherType = shadowsocks.CipherType_CHACHA20_POLY1305
|
||||
case "xchacha20-poly1305":
|
||||
ssCipherType = shadowsocks.CipherType_XCHACHA20_POLY1305
|
||||
default:
|
||||
ssCipherType = shadowsocks.CipherType_NONE
|
||||
}
|
||||
|
||||
if ssCipherType != shadowsocks.CipherType_NONE {
|
||||
account = serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user["password"].(string),
|
||||
CipherType: ssCipherType,
|
||||
})
|
||||
} else {
|
||||
account = serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: user["password"].(string),
|
||||
Email: user["email"].(string),
|
||||
})
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user