mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-29 11:22:51 +00:00
Compare commits
11 commits
3c1634ca7c
...
6a17285935
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a17285935 | ||
|
|
4b03e9d919 | ||
|
|
7e9c3bdbaf | ||
|
|
957f3dbb54 | ||
|
|
05e60af283 | ||
|
|
5e40458116 | ||
|
|
baf6fdd29d | ||
|
|
45f78d3521 | ||
|
|
01f984e054 | ||
|
|
e4ba5ba53a | ||
|
|
6ff555c8bb |
30 changed files with 288 additions and 36 deletions
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
- 386
|
||||
- armv5
|
||||
- s390x
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.6.3
|
||||
2.6.5
|
||||
13
install.sh
13
install.sh
|
|
@ -40,19 +40,6 @@ arch() {
|
|||
|
||||
echo "Arch: $(arch)"
|
||||
|
||||
check_glibc_version() {
|
||||
glibc_version=$(ldd --version | head -n1 | awk '{print $NF}')
|
||||
|
||||
required_version="2.32"
|
||||
if [[ "$(printf '%s\n' "$required_version" "$glibc_version" | sort -V | head -n1)" != "$required_version" ]]; then
|
||||
echo -e "${red}GLIBC version $glibc_version is too old! Required: 2.32 or higher${plain}"
|
||||
echo "Please upgrade to a newer version of your operating system to get a higher GLIBC version."
|
||||
exit 1
|
||||
fi
|
||||
echo "GLIBC version: $glibc_version (meets requirement of 2.32+)"
|
||||
}
|
||||
check_glibc_version
|
||||
|
||||
install_base() {
|
||||
case "${release}" in
|
||||
ubuntu | debian | armbian)
|
||||
|
|
|
|||
|
|
@ -560,6 +560,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||
enableSessionResumption = false,
|
||||
certificates = [new TlsStreamSettings.Cert()],
|
||||
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
||||
echServerKeys = '',
|
||||
echForceQuery = 'none',
|
||||
settings = new TlsStreamSettings.Settings()
|
||||
) {
|
||||
super();
|
||||
|
|
@ -573,6 +575,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||
this.enableSessionResumption = enableSessionResumption;
|
||||
this.certs = certificates;
|
||||
this.alpn = alpn;
|
||||
this.echServerKeys = echServerKeys;
|
||||
this.echForceQuery = echForceQuery;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
|
|
@ -592,7 +596,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||
}
|
||||
|
||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains);
|
||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.echConfigList);
|
||||
}
|
||||
return new TlsStreamSettings(
|
||||
json.serverName,
|
||||
|
|
@ -605,6 +609,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||
json.enableSessionResumption,
|
||||
certs,
|
||||
json.alpn,
|
||||
json.echServerKeys,
|
||||
json.echForceQuery,
|
||||
settings,
|
||||
);
|
||||
}
|
||||
|
|
@ -621,6 +627,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||
enableSessionResumption: this.enableSessionResumption,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
alpn: this.alpn,
|
||||
echServerKeys: this.echServerKeys,
|
||||
echForceQuery: this.echForceQuery,
|
||||
settings: this.settings,
|
||||
};
|
||||
}
|
||||
|
|
@ -701,21 +709,25 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||
constructor(
|
||||
allowInsecure = false,
|
||||
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||
echConfigList = '',
|
||||
) {
|
||||
super();
|
||||
this.allowInsecure = allowInsecure;
|
||||
this.fingerprint = fingerprint;
|
||||
this.echConfigList = echConfigList;
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
return new TlsStreamSettings.Settings(
|
||||
json.allowInsecure,
|
||||
json.fingerprint,
|
||||
json.echConfigList,
|
||||
);
|
||||
}
|
||||
toJson() {
|
||||
return {
|
||||
allowInsecure: this.allowInsecure,
|
||||
fingerprint: this.fingerprint,
|
||||
echConfigList: this.echConfigList
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -1375,6 +1387,9 @@ class Inbound extends XrayCommonClass {
|
|||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||
params.set("sni", this.stream.tls.sni);
|
||||
}
|
||||
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||
}
|
||||
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
||||
params.set("flow", flow);
|
||||
}
|
||||
|
|
@ -1474,6 +1489,9 @@ class Inbound extends XrayCommonClass {
|
|||
if (this.stream.tls.settings.allowInsecure) {
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||
params.set("sni", this.stream.tls.sni);
|
||||
}
|
||||
|
|
@ -1552,6 +1570,9 @@ class Inbound extends XrayCommonClass {
|
|||
if (this.stream.tls.settings.allowInsecure) {
|
||||
params.set("allowInsecure", "1");
|
||||
}
|
||||
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||
params.set("sni", this.stream.tls.sni);
|
||||
}
|
||||
|
|
@ -2291,12 +2312,14 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||
protocol,
|
||||
address,
|
||||
port,
|
||||
portMap = [],
|
||||
network = 'tcp,udp',
|
||||
followRedirect = false
|
||||
) {
|
||||
super(protocol);
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.portMap = portMap;
|
||||
this.network = network;
|
||||
this.followRedirect = followRedirect;
|
||||
}
|
||||
|
|
@ -2306,6 +2329,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||
Protocols.DOKODEMO,
|
||||
json.address,
|
||||
json.port,
|
||||
XrayCommonClass.toHeaders(json.portMap),
|
||||
json.network,
|
||||
json.followRedirect,
|
||||
);
|
||||
|
|
@ -2315,6 +2339,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||
return {
|
||||
address: this.address,
|
||||
port: this.port,
|
||||
portMap: XrayCommonClass.toV2Headers(this.portMap, false),
|
||||
network: this.network,
|
||||
followRedirect: this.followRedirect,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -354,13 +354,15 @@ class TlsStreamSettings extends CommonClass {
|
|||
serverName = '',
|
||||
alpn = [],
|
||||
fingerprint = '',
|
||||
allowInsecure = false
|
||||
allowInsecure = false,
|
||||
echConfigList = '',
|
||||
) {
|
||||
super();
|
||||
this.serverName = serverName;
|
||||
this.alpn = alpn;
|
||||
this.fingerprint = fingerprint;
|
||||
this.allowInsecure = allowInsecure;
|
||||
this.echConfigList = echConfigList;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
|
|
@ -369,6 +371,7 @@ class TlsStreamSettings extends CommonClass {
|
|||
json.alpn,
|
||||
json.fingerprint,
|
||||
json.allowInsecure,
|
||||
json.echConfigList,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -378,6 +381,7 @@ class TlsStreamSettings extends CommonClass {
|
|||
alpn: this.alpn,
|
||||
fingerprint: this.fingerprint,
|
||||
allowInsecure: this.allowInsecure,
|
||||
echConfigList: this.echConfigList
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -782,7 +786,8 @@ class Outbound extends CommonClass {
|
|||
let alpn = url.searchParams.get('alpn');
|
||||
let allowInsecure = url.searchParams.get('allowInsecure');
|
||||
let sni = url.searchParams.get('sni') ?? '';
|
||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
||||
let ech = url.searchParams.get('ech') ?? '';
|
||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
|
||||
}
|
||||
|
||||
if (security == 'reality') {
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
|
||||
if err == nil && needRestart {
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
|
@ -126,8 +126,8 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, err)
|
||||
if err == nil && needRestart {
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
|
@ -152,8 +152,8 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, err)
|
||||
if err == nil && needRestart {
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
|
@ -342,25 +342,25 @@ func (a *InboundController) onlines(c *gin.Context) {
|
|||
|
||||
func (a *InboundController) updateClientTraffic(c *gin.Context) {
|
||||
email := c.Param("email")
|
||||
|
||||
|
||||
// Define the request structure for traffic update
|
||||
type TrafficUpdateRequest struct {
|
||||
Upload int64 `json:"upload"`
|
||||
Download int64 `json:"download"`
|
||||
}
|
||||
|
||||
|
||||
var request TrafficUpdateRequest
|
||||
err := c.ShouldBindJSON(&request)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
err = a.inboundService.UpdateClientTrafficByEmail(email, request.Upload, request.Download)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,13 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||
g.POST("/installXray/:version", a.installXray)
|
||||
g.POST("/updateGeofile/:fileName", a.updateGeofile)
|
||||
g.POST("/logs/:count", a.getLogs)
|
||||
g.POST("/xraylogs/:count", a.getXrayLogs)
|
||||
g.POST("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.POST("/importDB", a.importDB)
|
||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
g.POST("/getNewmldsa65", a.getNewmldsa65)
|
||||
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||
}
|
||||
|
||||
func (a *ServerController) refreshStatus() {
|
||||
|
|
@ -133,6 +135,12 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
|||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getXrayLogs(c *gin.Context) {
|
||||
count := c.Param("count")
|
||||
logs := a.serverService.GetXrayLogs(count)
|
||||
jsonObj(c, logs, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||
configJson, err := a.serverService.GetConfigJson()
|
||||
if err != nil {
|
||||
|
|
@ -208,3 +216,13 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) {
|
|||
}
|
||||
jsonObj(c, cert, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getNewEchCert(c *gin.Context) {
|
||||
sni := c.PostForm("sni")
|
||||
cert, err := a.serverService.GetNewEchCert(sni)
|
||||
if err != nil {
|
||||
jsonMsg(c, "get ech certificate", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, cert, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||
<a-form-item v-if="client.email && app.subSettings?.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,19 @@
|
|||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
|
||||
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
|
||||
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,21 @@
|
|||
<a-switch v-model="cert.buildChain"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='ECH key'>
|
||||
<a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='ECH config'>
|
||||
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='ECH force query'>
|
||||
<a-select v-model="inbound.stream.tls.echForceQuery"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
|
|
|
|||
|
|
@ -167,7 +167,10 @@
|
|||
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
|
||||
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openXrayLogs()"></a-icon>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</span>
|
||||
|
|
@ -179,6 +182,10 @@
|
|||
</template>
|
||||
</template>
|
||||
<template #actions>
|
||||
<a-space v-if="app.ipLimitEnable" direction="horizontal" @click="openXrayLogs()" :style="{ justifyContent: 'center' }">
|
||||
<a-icon type="bars"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
|
||||
<a-icon type="poweroff"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
|
||||
|
|
@ -422,6 +429,40 @@
|
|||
</a-form>
|
||||
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
|
||||
</a-modal>
|
||||
<a-modal id="xraylog-modal"
|
||||
v-model="xraylogModal.visible"
|
||||
:closable="true" @cancel="() => xraylogModal.visible = false"
|
||||
:class="themeSwitcher.currentTheme"
|
||||
width="80vw"
|
||||
footer="">
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.logs" }}
|
||||
<a-icon :spin="xraylogModal.loading"
|
||||
type="sync"
|
||||
:style="{ verticalAlign: 'middle', marginLeft: '10px' }"
|
||||
:disabled="xraylogModal.loading"
|
||||
@click="openXrayLogs()">
|
||||
</a-icon>
|
||||
</template>
|
||||
<a-form layout="inline">
|
||||
<a-form-item :style="{ marginRight: '0.5rem' }">
|
||||
<a-input-group compact>
|
||||
<a-select size="small" v-model="xraylogModal.rows" :style="{ width: '70px' }"
|
||||
@change="openXrayLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
<a-select-option value="50">50</a-select-option>
|
||||
<a-select-option value="100">100</a-select-option>
|
||||
<a-select-option value="500">500</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item :style="{ float: 'right' }">
|
||||
<a-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="xraylogModal.formattedLogs"></div>
|
||||
</a-modal>
|
||||
<a-modal id="backup-modal"
|
||||
v-model="backupModal.visible"
|
||||
title='{{ i18n "pages.index.backupTitle" }}'
|
||||
|
|
@ -606,6 +647,57 @@
|
|||
},
|
||||
};
|
||||
|
||||
const xraylogModal = {
|
||||
visible: false,
|
||||
logs: [],
|
||||
rows: 20,
|
||||
loading: false,
|
||||
show(logs) {
|
||||
this.visible = true;
|
||||
this.logs = logs;
|
||||
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||
},
|
||||
formatLogs(logs) {
|
||||
let formattedLogs = '';
|
||||
|
||||
logs.forEach((log, index) => {
|
||||
if(index > 0) formattedLogs += '<br>';
|
||||
|
||||
const parts = log.split(' ');
|
||||
|
||||
if(parts.length === 9) {
|
||||
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
|
||||
const from = `<b>${parts[3]}</b>`;
|
||||
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
|
||||
|
||||
let outboundColor = '';
|
||||
if (parts[8].startsWith('blocked')) {
|
||||
outboundColor = ' style="color: #e04141;"';
|
||||
}
|
||||
else if (!parts[8].startsWith('direct')) {
|
||||
outboundColor = ' style="color: #3c89e8;"';
|
||||
}
|
||||
|
||||
formattedLogs += `<span${outboundColor}>
|
||||
${dateTime}
|
||||
${parts[2]}
|
||||
${from}
|
||||
${parts[4]}
|
||||
${to}
|
||||
${parts.slice(6).join(' ')}
|
||||
</span>`;
|
||||
} else {
|
||||
formattedLogs += `<span>${parts.join(' ')}</span>`;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedLogs;
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
const backupModal = {
|
||||
visible: false,
|
||||
show() {
|
||||
|
|
@ -629,10 +721,12 @@
|
|||
status: new Status(),
|
||||
versionModal,
|
||||
logModal,
|
||||
xraylogModal,
|
||||
backupModal,
|
||||
loadingTip: '{{ i18n "loading"}}',
|
||||
showAlert: false,
|
||||
showIp: false
|
||||
showIp: false,
|
||||
ipLimitEnable: false,
|
||||
},
|
||||
methods: {
|
||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||
|
|
@ -721,6 +815,16 @@
|
|||
await PromiseUtil.sleep(500);
|
||||
logModal.loading = false;
|
||||
},
|
||||
async openXrayLogs(){
|
||||
xraylogModal.loading = true;
|
||||
const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
xraylogModal.show(msg.obj);
|
||||
await PromiseUtil.sleep(500);
|
||||
xraylogModal.loading = false;
|
||||
},
|
||||
async openConfig() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/getConfigJson');
|
||||
|
|
@ -773,6 +877,12 @@
|
|||
if (window.location.protocol !== "https:") {
|
||||
this.showAlert = true;
|
||||
}
|
||||
|
||||
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||
if (msg.success) {
|
||||
this.ipLimitEnable = msg.obj.ipLimitEnable;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
await this.getStatus();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.subSettings.enable">
|
||||
<a-form-item v-if="app.subSettings?.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@
|
|||
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.ipLimitEnable">
|
||||
<tr v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
|
||||
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
||||
<td>
|
||||
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
||||
|
|
|
|||
|
|
@ -152,6 +152,16 @@
|
|||
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
||||
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
||||
},
|
||||
async getNewEchCert() {
|
||||
inModal.loading(true);
|
||||
const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
|
||||
inModal.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
|
||||
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
</a-space>
|
||||
</template>
|
||||
<tr-qr-modal class="qr-modal">
|
||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||
<template v-if="app.subSettings?.enable && qrModal.subId">
|
||||
<tr-qr-box class="qr-box">
|
||||
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg-sub">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -329,7 +330,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if major > 25 || (major == 25 && minor > 7) || (major == 25 && minor == 7 && patch >= 26) {
|
||||
if major > 25 || (major == 25 && minor > 8) || (major == 25 && minor == 8 && patch >= 3) {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
|
|
@ -481,6 +482,37 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
|||
return lines
|
||||
}
|
||||
|
||||
func (s *ServerService) GetXrayLogs(count string) []string {
|
||||
c, _ := strconv.Atoi(count)
|
||||
var lines []string
|
||||
|
||||
pathToAccessLog, err := xray.GetAccessLogPath()
|
||||
if err != nil {
|
||||
return lines
|
||||
}
|
||||
|
||||
file, err := os.Open(pathToAccessLog)
|
||||
if err != nil {
|
||||
return lines
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.TrimSpace(line) == "" || strings.Contains(line, "api -> api") {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
if len(lines) > c {
|
||||
lines = lines[len(lines)-c:]
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (any, error) {
|
||||
config, err := s.xrayService.GetXrayConfig()
|
||||
if err != nil {
|
||||
|
|
@ -743,3 +775,27 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
|
|||
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
if len(lines) < 4 {
|
||||
return nil, common.NewError("invalid ech cert")
|
||||
}
|
||||
|
||||
configList := lines[1]
|
||||
serverKeys := lines[3]
|
||||
|
||||
return map[string]interface{}{
|
||||
"echServerKeys": serverKeys,
|
||||
"echConfigList": configList,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "ملاحظة"
|
||||
"protocol" = "بروتوكول"
|
||||
"port" = "بورت"
|
||||
"portMap" = "خريطة البورت"
|
||||
"traffic" = "الترافيك"
|
||||
"details" = "تفاصيل"
|
||||
"transportConfig" = "نقل"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Remark"
|
||||
"protocol" = "Protocol"
|
||||
"port" = "Port"
|
||||
"portMap" = "Port Mapping"
|
||||
"traffic" = "Traffic"
|
||||
"details" = "Details"
|
||||
"transportConfig" = "Transport"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Notas"
|
||||
"protocol" = "Protocolo"
|
||||
"port" = "Puerto"
|
||||
"portMap" = "Puertos de Destino"
|
||||
"traffic" = "Tráfico"
|
||||
"details" = "Detalles"
|
||||
"transportConfig" = "Transporte"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "نام"
|
||||
"protocol" = "پروتکل"
|
||||
"port" = "پورت"
|
||||
"portMap" = "پورتهای نظیر"
|
||||
"traffic" = "ترافیک"
|
||||
"details" = "توضیحات"
|
||||
"transportConfig" = "نحوه اتصال"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Catatan"
|
||||
"protocol" = "Protokol"
|
||||
"port" = "Port"
|
||||
"portMap" = "Port Mapping"
|
||||
"traffic" = "Traffic"
|
||||
"details" = "Rincian"
|
||||
"transportConfig" = "Transport"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "備考"
|
||||
"protocol" = "プロトコル"
|
||||
"port" = "ポート"
|
||||
"portMap" = "ポートマッピング"
|
||||
"traffic" = "トラフィック"
|
||||
"details" = "詳細情報"
|
||||
"transportConfig" = "トランスポート設定"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Observação"
|
||||
"protocol" = "Protocolo"
|
||||
"port" = "Porta"
|
||||
"portMap" = "Porta Mapeada"
|
||||
"traffic" = "Tráfego"
|
||||
"details" = "Detalhes"
|
||||
"transportConfig" = "Transporte"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Примечание"
|
||||
"protocol" = "Протокол"
|
||||
"port" = "Порт"
|
||||
"portMap" = "Порт-маппинг"
|
||||
"traffic" = "Трафик"
|
||||
"details" = "Подробнее"
|
||||
"transportConfig" = "Транспорт"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Açıklama"
|
||||
"protocol" = "Protokol"
|
||||
"port" = "Port"
|
||||
"portMap" = "Port Atama"
|
||||
"traffic" = "Trafik"
|
||||
"details" = "Detaylar"
|
||||
"transportConfig" = "Taşıma"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Примітка"
|
||||
"protocol" = "Протокол"
|
||||
"port" = "Порт"
|
||||
"portMap" = "Порт-перехід"
|
||||
"traffic" = "Трафік"
|
||||
"details" = "Деталі"
|
||||
"transportConfig" = "Транспорт"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "Chú thích"
|
||||
"protocol" = "Giao thức"
|
||||
"port" = "Cổng"
|
||||
"portMap" = "Cổng tạo"
|
||||
"traffic" = "Lưu lượng"
|
||||
"details" = "Chi tiết"
|
||||
"transportConfig" = "Giao vận"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "备注"
|
||||
"protocol" = "协议"
|
||||
"port" = "端口"
|
||||
"portMap" = "端口映射"
|
||||
"traffic" = "流量"
|
||||
"details" = "详细信息"
|
||||
"transportConfig" = "传输配置"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
"remark" = "備註"
|
||||
"protocol" = "協議"
|
||||
"port" = "埠"
|
||||
"portMap" = "埠映射"
|
||||
"traffic" = "流量"
|
||||
"details" = "詳細資訊"
|
||||
"transportConfig" = "傳輸配置"
|
||||
|
|
|
|||
Loading…
Reference in a new issue