mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-10-27 10:30:08 +00:00
Compare commits
10 commits
a7f460213d
...
a1c0231abc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1c0231abc | ||
|
|
10d57d9502 | ||
|
|
7d75cfc947 | ||
|
|
c418e992ca | ||
|
|
c2d6dd923f | ||
|
|
723ec25fb2 | ||
|
|
7dc52e9a53 | ||
|
|
fe9f0d1d0e | ||
|
|
18d74d54ca | ||
|
|
c7ba6ae909 |
33 changed files with 469 additions and 195 deletions
76
.github/workflows/release.yml
vendored
76
.github/workflows/release.yml
vendored
|
|
@ -146,3 +146,79 @@ jobs:
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
overwrite: true
|
overwrite: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# Windows Build
|
||||||
|
# =================================
|
||||||
|
build-windows:
|
||||||
|
name: Build for Windows
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- amd64
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Build 3X-UI for Windows
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$env:CGO_ENABLED="1"
|
||||||
|
$env:GOOS="windows"
|
||||||
|
$env:GOARCH="amd64"
|
||||||
|
go build -ldflags "-w -s" -o xui-release.exe -v main.go
|
||||||
|
|
||||||
|
mkdir x-ui
|
||||||
|
Copy-Item xui-release.exe x-ui\
|
||||||
|
mkdir x-ui\bin
|
||||||
|
cd x-ui\bin
|
||||||
|
|
||||||
|
# Download Xray for Windows
|
||||||
|
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.6.8/"
|
||||||
|
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
||||||
|
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
||||||
|
Remove-Item "Xray-windows-64.zip"
|
||||||
|
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat"
|
||||||
|
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat"
|
||||||
|
Rename-Item xray.exe xray-windows-amd64.exe
|
||||||
|
cd ..
|
||||||
|
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
- name: Package to Zip
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: x-ui-windows-amd64
|
||||||
|
path: ./x-ui-windows-amd64.zip
|
||||||
|
|
||||||
|
- name: Upload files to GH release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||||
|
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file: x-ui-windows-amd64.zip
|
||||||
|
asset_name: x-ui-windows-amd64.zip
|
||||||
|
overwrite: true
|
||||||
|
prerelease: true
|
||||||
|
|
@ -12,11 +12,11 @@ type Protocol string
|
||||||
const (
|
const (
|
||||||
VMESS Protocol = "vmess"
|
VMESS Protocol = "vmess"
|
||||||
VLESS Protocol = "vless"
|
VLESS Protocol = "vless"
|
||||||
DOKODEMO Protocol = "dokodemo-door"
|
Tunnel Protocol = "tunnel"
|
||||||
HTTP Protocol = "http"
|
HTTP Protocol = "http"
|
||||||
Trojan Protocol = "trojan"
|
Trojan Protocol = "trojan"
|
||||||
Shadowsocks Protocol = "shadowsocks"
|
Shadowsocks Protocol = "shadowsocks"
|
||||||
Socks Protocol = "socks"
|
Mixed Protocol = "mixed"
|
||||||
WireGuard Protocol = "wireguard"
|
WireGuard Protocol = "wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,7 +36,8 @@ type Inbound struct {
|
||||||
Remark string `json:"remark" form:"remark"`
|
Remark string `json:"remark" form:"remark"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
PeriodicTrafficReset string `json:"periodicTrafficReset" form:"periodicTrafficReset" gorm:"default:never"`
|
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never"`
|
||||||
|
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
|
||||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||||
|
|
||||||
// config part
|
// config part
|
||||||
|
|
@ -91,21 +92,23 @@ type Setting struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Security string `json:"security"`
|
Security string `json:"security"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
LimitIP int `json:"limitIp"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never"`
|
||||||
TgID int64 `json:"tgId" form:"tgId"`
|
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
Comment string `json:"comment" form:"comment"`
|
TgID int64 `json:"tgId" form:"tgId"`
|
||||||
Reset int `json:"reset" form:"reset"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
CreatedAt int64 `json:"created_at,omitempty"`
|
Comment string `json:"comment" form:"comment"`
|
||||||
UpdatedAt int64 `json:"updated_at,omitempty"`
|
Reset int `json:"reset" form:"reset"`
|
||||||
|
CreatedAt int64 `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt int64 `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VLESSSettings struct {
|
type VLESSSettings struct {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"port": 10808,
|
"port": 10808,
|
||||||
"protocol": "socks",
|
"protocol": "mixed",
|
||||||
"settings": {
|
"settings": {
|
||||||
"auth": "noauth",
|
"auth": "noauth",
|
||||||
"udp": true,
|
"udp": true,
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
],
|
],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"tag": "socks"
|
"tag": "mixed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port": 10809,
|
"port": 10809,
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ class DBInbound {
|
||||||
return this.protocol === Protocols.SHADOWSOCKS;
|
return this.protocol === Protocols.SHADOWSOCKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isSocks() {
|
get isMixed() {
|
||||||
return this.protocol === Protocols.SOCKS;
|
return this.protocol === Protocols.MIXED;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isHTTP() {
|
get isHTTP() {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ const Protocols = {
|
||||||
VLESS: 'vless',
|
VLESS: 'vless',
|
||||||
TROJAN: 'trojan',
|
TROJAN: 'trojan',
|
||||||
SHADOWSOCKS: 'shadowsocks',
|
SHADOWSOCKS: 'shadowsocks',
|
||||||
DOKODEMO: 'dokodemo-door',
|
TUNNEL: 'tunnel',
|
||||||
SOCKS: 'socks',
|
MIXED: 'mixed',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
WIREGUARD: 'wireguard',
|
WIREGUARD: 'wireguard',
|
||||||
};
|
};
|
||||||
|
|
@ -729,7 +729,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
show = false,
|
show = false,
|
||||||
xver = 0,
|
xver = 0,
|
||||||
dest = 'google.com:443',
|
target = 'google.com:443',
|
||||||
serverNames = 'google.com,www.google.com',
|
serverNames = 'google.com,www.google.com',
|
||||||
privateKey = '',
|
privateKey = '',
|
||||||
minClientVer = '',
|
minClientVer = '',
|
||||||
|
|
@ -742,7 +742,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
super();
|
super();
|
||||||
this.show = show;
|
this.show = show;
|
||||||
this.xver = xver;
|
this.xver = xver;
|
||||||
this.dest = dest;
|
this.target = target;
|
||||||
this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames;
|
this.serverNames = Array.isArray(serverNames) ? serverNames.join(",") : serverNames;
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
this.minClientVer = minClientVer;
|
this.minClientVer = minClientVer;
|
||||||
|
|
@ -767,7 +767,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
json.show,
|
json.show,
|
||||||
json.xver,
|
json.xver,
|
||||||
json.dest,
|
json.target,
|
||||||
json.serverNames,
|
json.serverNames,
|
||||||
json.privateKey,
|
json.privateKey,
|
||||||
json.minClientVer,
|
json.minClientVer,
|
||||||
|
|
@ -783,7 +783,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
return {
|
return {
|
||||||
show: this.show,
|
show: this.show,
|
||||||
xver: this.xver,
|
xver: this.xver,
|
||||||
dest: this.dest,
|
target: this.target,
|
||||||
serverNames: this.serverNames.split(","),
|
serverNames: this.serverNames.split(","),
|
||||||
privateKey: this.privateKey,
|
privateKey: this.privateKey,
|
||||||
minClientVer: this.minClientVer,
|
minClientVer: this.minClientVer,
|
||||||
|
|
@ -1712,8 +1712,8 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||||
case Protocols.VLESS: return new Inbound.VLESSSettings(protocol);
|
case Protocols.VLESS: return new Inbound.VLESSSettings(protocol);
|
||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
||||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol);
|
||||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
case Protocols.MIXED: return new Inbound.MixedSettings(protocol);
|
||||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||||
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
||||||
default: return null;
|
default: return null;
|
||||||
|
|
@ -1726,8 +1726,8 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||||
case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json);
|
case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json);
|
||||||
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json);
|
||||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
case Protocols.MIXED: return Inbound.MixedSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||||
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
|
|
@ -2327,7 +2327,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.DokodemoSettings = class extends Inbound.Settings {
|
Inbound.TunnelSettings = class extends Inbound.Settings {
|
||||||
constructor(
|
constructor(
|
||||||
protocol,
|
protocol,
|
||||||
address,
|
address,
|
||||||
|
|
@ -2345,8 +2345,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.DokodemoSettings(
|
return new Inbound.TunnelSettings(
|
||||||
Protocols.DOKODEMO,
|
Protocols.TUNNEL,
|
||||||
json.address,
|
json.address,
|
||||||
json.port,
|
json.port,
|
||||||
XrayCommonClass.toHeaders(json.portMap),
|
XrayCommonClass.toHeaders(json.portMap),
|
||||||
|
|
@ -2366,8 +2366,8 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
Inbound.MixedSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, auth = 'password', accounts = [new Inbound.SocksSettings.SocksAccount()], udp = false, ip = '127.0.0.1') {
|
constructor(protocol, auth = 'password', accounts = [new Inbound.MixedSettings.SocksAccount()], udp = false, ip = '127.0.0.1') {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
|
|
@ -2387,11 +2387,11 @@ Inbound.SocksSettings = class extends Inbound.Settings {
|
||||||
let accounts;
|
let accounts;
|
||||||
if (json.auth === 'password') {
|
if (json.auth === 'password') {
|
||||||
accounts = json.accounts.map(
|
accounts = json.accounts.map(
|
||||||
account => Inbound.SocksSettings.SocksAccount.fromJson(account)
|
account => Inbound.MixedSettings.SocksAccount.fromJson(account)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return new Inbound.SocksSettings(
|
return new Inbound.MixedSettings(
|
||||||
Protocols.SOCKS,
|
Protocols.MIXED,
|
||||||
json.auth,
|
json.auth,
|
||||||
accounts,
|
accounts,
|
||||||
json.udp,
|
json.udp,
|
||||||
|
|
@ -2408,7 +2408,7 @@ Inbound.SocksSettings = class extends Inbound.Settings {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass {
|
Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass {
|
||||||
constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) {
|
constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) {
|
||||||
super();
|
super();
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
@ -2416,7 +2416,7 @@ Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.SocksSettings.SocksAccount(json.user, json.pass);
|
return new Inbound.MixedSettings.SocksAccount(json.user, json.pass);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const Protocols = {
|
||||||
VLESS: "vless",
|
VLESS: "vless",
|
||||||
Trojan: "trojan",
|
Trojan: "trojan",
|
||||||
Shadowsocks: "shadowsocks",
|
Shadowsocks: "shadowsocks",
|
||||||
Socks: "socks",
|
Mixed: "mixed",
|
||||||
HTTP: "http",
|
HTTP: "http",
|
||||||
Wireguard: "wireguard"
|
Wireguard: "wireguard"
|
||||||
};
|
};
|
||||||
|
|
@ -643,7 +643,7 @@ class Outbound extends CommonClass {
|
||||||
Protocols.Trojan,
|
Protocols.Trojan,
|
||||||
Protocols.Shadowsocks,
|
Protocols.Shadowsocks,
|
||||||
Protocols.HTTP,
|
Protocols.HTTP,
|
||||||
Protocols.Socks
|
Protocols.Mixed
|
||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,7 +652,7 @@ class Outbound extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasServers() {
|
hasServers() {
|
||||||
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAddressPort() {
|
hasAddressPort() {
|
||||||
|
|
@ -662,13 +662,13 @@ class Outbound extends CommonClass {
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
Protocols.Trojan,
|
Protocols.Trojan,
|
||||||
Protocols.Shadowsocks,
|
Protocols.Shadowsocks,
|
||||||
Protocols.Socks,
|
Protocols.Mixed,
|
||||||
Protocols.HTTP
|
Protocols.HTTP
|
||||||
].includes(this.protocol);
|
].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasUsername() {
|
hasUsername() {
|
||||||
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
return [Protocols.Mixed, Protocols.HTTP].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
|
@ -847,7 +847,7 @@ Outbound.Settings = class extends CommonClass {
|
||||||
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
||||||
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
||||||
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||||
case Protocols.Socks: return new Outbound.SocksSettings();
|
case Protocols.Mixed: return new Outbound.MixedSettings();
|
||||||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||||
default: return null;
|
default: return null;
|
||||||
|
|
@ -863,7 +863,7 @@ Outbound.Settings = class extends CommonClass {
|
||||||
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
||||||
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
case Protocols.Mixed: return Outbound.MixedSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
|
|
@ -1141,7 +1141,7 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Outbound.SocksSettings = class extends CommonClass {
|
Outbound.MixedSettings = class extends CommonClass {
|
||||||
constructor(address, port, user, pass) {
|
constructor(address, port, user, pass) {
|
||||||
super();
|
super();
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
|
@ -1153,7 +1153,7 @@ Outbound.SocksSettings = class extends CommonClass {
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
let servers = json.servers;
|
let servers = json.servers;
|
||||||
if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }];
|
if (ObjectUtil.isArrEmpty(servers)) servers = [{ users: [{}] }];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.MixedSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
serverController *ServerController
|
||||||
Tgbot service.Tgbot
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,43 +20,22 @@ func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/panel/api/inbounds")
|
// Main API group
|
||||||
g.Use(a.checkLogin)
|
api := g.Group("/panel/api")
|
||||||
|
api.Use(a.checkLogin)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
// Inbounds API
|
||||||
|
inbounds := api.Group("/inbounds")
|
||||||
|
a.inboundController = NewInboundController(inbounds)
|
||||||
|
|
||||||
inboundRoutes := []struct {
|
// Server API
|
||||||
Method string
|
server := api.Group("/server")
|
||||||
Path string
|
a.serverController = NewServerController(server)
|
||||||
Handler gin.HandlerFunc
|
|
||||||
}{
|
|
||||||
{"GET", "/createbackup", a.createBackup},
|
|
||||||
{"GET", "/list", a.inboundController.getInbounds},
|
|
||||||
{"GET", "/get/:id", a.inboundController.getInbound},
|
|
||||||
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
|
||||||
{"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById},
|
|
||||||
{"POST", "/add", a.inboundController.addInbound},
|
|
||||||
{"POST", "/del/:id", a.inboundController.delInbound},
|
|
||||||
{"POST", "/update/:id", a.inboundController.updateInbound},
|
|
||||||
{"POST", "/clientIps/:email", a.inboundController.getClientIps},
|
|
||||||
{"POST", "/clearClientIps/:email", a.inboundController.clearClientIps},
|
|
||||||
{"POST", "/addClient", a.inboundController.addInboundClient},
|
|
||||||
{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient},
|
|
||||||
{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient},
|
|
||||||
{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic},
|
|
||||||
{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics},
|
|
||||||
{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics},
|
|
||||||
{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients},
|
|
||||||
{"POST", "/onlines", a.inboundController.onlines},
|
|
||||||
{"POST", "/lastOnline", a.inboundController.lastOnline},
|
|
||||||
{"POST", "/updateClientTraffic/:email", a.inboundController.updateClientTraffic},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range inboundRoutes {
|
// Extra routes
|
||||||
g.Handle(route.Method, route.Path, route.Handler)
|
api.GET("/backuptotgbot", a.BackuptoTgbot)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) createBackup(c *gin.Context) {
|
func (a *APIController) BackuptoTgbot(c *gin.Context) {
|
||||||
a.Tgbot.SendBackupToAdmins()
|
a.Tgbot.SendBackupToAdmins()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,12 @@ func NewInboundController(g *gin.RouterGroup) *InboundController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/inbound")
|
|
||||||
|
|
||||||
g.POST("/list", a.getInbounds)
|
g.GET("/list", a.getInbounds)
|
||||||
|
g.GET("/get/:id", a.getInbound)
|
||||||
|
g.GET("/getClientTraffics/:email", a.getClientTraffics)
|
||||||
|
g.GET("/getClientTrafficsById/:id", a.getClientTrafficsById)
|
||||||
|
|
||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
|
|
@ -41,6 +44,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
g.POST("/import", a.importInbound)
|
g.POST("/import", a.importInbound)
|
||||||
g.POST("/onlines", a.onlines)
|
g.POST("/onlines", a.onlines)
|
||||||
|
g.POST("/lastOnline", a.lastOnline)
|
||||||
|
g.POST("/updateClientTraffic/:email", a.updateClientTraffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,17 @@ func NewServerController(g *gin.RouterGroup) *ServerController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/server")
|
|
||||||
|
|
||||||
g.Use(a.checkLogin)
|
g.GET("/status", a.status)
|
||||||
g.POST("/status", a.status)
|
g.GET("/getXrayVersion", a.getXrayVersion)
|
||||||
g.POST("/getXrayVersion", a.getXrayVersion)
|
g.GET("/getConfigJson", a.getConfigJson)
|
||||||
|
g.GET("/getDb", a.getDb)
|
||||||
|
g.GET("/getNewUUID", a.getNewUUID)
|
||||||
|
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
|
g.GET("/getNewmldsa65", a.getNewmldsa65)
|
||||||
|
g.GET("/getNewmlkem768", a.getNewmlkem768)
|
||||||
|
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
|
||||||
|
|
||||||
g.POST("/stopXrayService", a.stopXrayService)
|
g.POST("/stopXrayService", a.stopXrayService)
|
||||||
g.POST("/restartXrayService", a.restartXrayService)
|
g.POST("/restartXrayService", a.restartXrayService)
|
||||||
g.POST("/installXray/:version", a.installXray)
|
g.POST("/installXray/:version", a.installXray)
|
||||||
|
|
@ -49,13 +55,8 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/updateGeofile/:fileName", a.updateGeofile)
|
g.POST("/updateGeofile/:fileName", a.updateGeofile)
|
||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/xraylogs/:count", a.getXrayLogs)
|
g.POST("/xraylogs/:count", a.getXrayLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
|
||||||
g.GET("/getDb", a.getDb)
|
|
||||||
g.POST("/importDB", a.importDB)
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
|
||||||
g.POST("/getNewmldsa65", a.getNewmldsa65)
|
|
||||||
g.POST("/getNewEchCert", a.getNewEchCert)
|
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||||
g.POST("/getNewVlessEnc", a.getNewVlessEnc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
|
|
@ -276,3 +277,22 @@ func (a *ServerController) getNewVlessEnc(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonObj(c, out, nil)
|
jsonObj(c, out, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewUUID(c *gin.Context) {
|
||||||
|
uuidResp, err := a.serverService.GetNewUUID()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Failed to generate UUID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObj(c, uuidResp, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewmlkem768(c *gin.Context) {
|
||||||
|
out, err := a.serverService.GetNewmlkem768()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Failed to generate mlkem768 keys", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, out, nil)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ type XUIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
serverController *ServerController
|
||||||
settingController *SettingController
|
settingController *SettingController
|
||||||
xraySettingController *XraySettingController
|
xraySettingController *XraySettingController
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +29,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/xray", a.xraySettings)
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
|
a.serverController = NewServerController(g)
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
a.xraySettingController = NewXraySettingController(g)
|
a.xraySettingController = NewXraySettingController(g)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,14 +101,14 @@
|
||||||
{{template "form/shadowsocks"}}
|
{{template "form/shadowsocks"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- dokodemo-door -->
|
<!-- tunnel -->
|
||||||
<template v-if="inbound.protocol === Protocols.DOKODEMO">
|
<template v-if="inbound.protocol === Protocols.TUNNEL">
|
||||||
{{template "form/dokodemo"}}
|
{{template "form/tunnel"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- socks -->
|
<!-- mixed -->
|
||||||
<template v-if="inbound.protocol === Protocols.SOCKS">
|
<template v-if="inbound.protocol === Protocols.MIXED">
|
||||||
{{template "form/socks"}}
|
{{template "form/mixed"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- http -->
|
<!-- http -->
|
||||||
|
|
|
||||||
|
|
@ -241,9 +241,9 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
<!-- Servers (trojan/shadowsocks/mixed/http) settings -->
|
||||||
<template v-if="outbound.hasServers()">
|
<template v-if="outbound.hasServers()">
|
||||||
<!-- http / socks -->
|
<!-- http / mixed -->
|
||||||
<template v-if="outbound.hasUsername()">
|
<template v-if="outbound.hasUsername()">
|
||||||
<a-form-item label='{{ i18n "username" }}'>
|
<a-form-item label='{{ i18n "username" }}'>
|
||||||
<a-input v-model.trim="outbound.settings.user"></a-input>
|
<a-input v-model.trim="outbound.settings.user"></a-input>
|
||||||
|
|
@ -441,6 +441,9 @@
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="ECH Config List">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="Allow Insecure">
|
<a-form-item label="Allow Insecure">
|
||||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{define "form/dokodemo"}}
|
{{define "form/tunnel"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{define "form/socks"}}
|
{{define "form/mixed"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())"></a-button>
|
<a-button icon="plus" size="small" @click="inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())"></a-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Authentication">
|
<a-form-item label="Authentication">
|
||||||
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="undefined">None</a-select-option>
|
|
||||||
<a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
|
<a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
|
||||||
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
|
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|
@ -31,17 +30,17 @@
|
||||||
<a-input v-model.trim="inbound.settings.decryption"></a-input>
|
<a-input v-model.trim="inbound.settings.decryption"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="encryption">
|
<a-form-item label="encryption">
|
||||||
<a-input v-model="inbound.settings.encryption" disabled></a-input>
|
<a-input v-model="inbound.settings.encryption"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
|
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
|
||||||
<a-button danger @click="clearKeys">Clear</a-button>
|
<a-button danger @click="clearVlessEnc">Clear</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="inbound.isTcp && !inbound.settings.encryption">
|
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
<a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Dest (Target)'>
|
<a-form-item label='Target'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.target"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='SNI'>
|
<a-form-item label='SNI'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||||
|
|
@ -48,7 +48,10 @@
|
||||||
<a-textarea v-model="inbound.stream.reality.privateKey"></a-textarea>
|
<a-textarea v-model="inbound.stream.reality.privateKey"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
<a-space>
|
||||||
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||||
|
<a-button danger @click="clearX25519Cert">Clear</a-button>
|
||||||
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="mldsa65 Seed">
|
<a-form-item label="mldsa65 Seed">
|
||||||
<a-textarea v-model="inbound.stream.reality.mldsa65Seed"></a-textarea>
|
<a-textarea v-model="inbound.stream.reality.mldsa65Seed"></a-textarea>
|
||||||
|
|
@ -57,7 +60,10 @@
|
||||||
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
|
<a-space>
|
||||||
|
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
|
||||||
|
<a-button danger @click="clearMldsa65">Clear</a-button>
|
||||||
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
<a-form-item label='{{ i18n "security" }}'>
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-radio-button v-if="inbound.canEnableReality() && !inbound.settings.encryption" value="reality">Reality</a-radio-button>
|
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
<a-radio-button v-if="!inbound.settings.encryption" value="tls">TLS</a-radio-button>
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<template v-if="inbound.stream.isTls && !inbound.settings.encryption">
|
<template v-if="inbound.stream.isTls">
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
@ -116,12 +116,15 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
|
<a-space>
|
||||||
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
|
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
|
||||||
|
<a-button danger @click="clearEchCert">Clear</a-button>
|
||||||
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<template v-if="inbound.stream.isReality && !inbound.settings.encryption">
|
<template v-if="inbound.stream.isReality">
|
||||||
{{template "form/realitySettings"}}
|
{{template "form/realitySettings"}}
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
||||||
|
|
@ -836,7 +836,7 @@
|
||||||
},
|
},
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
const msg = await HttpUtil.post('/panel/inbound/list');
|
const msg = await HttpUtil.get('/panel/api/inbounds/list');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
|
|
@ -851,7 +851,7 @@
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
async getOnlineUsers() {
|
async getOnlineUsers() {
|
||||||
const msg = await HttpUtil.post('/panel/inbound/onlines');
|
const msg = await HttpUtil.post('/panel/api/inbounds/onlines');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1106,7 +1106,7 @@
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.sniffing.toString(),
|
sniffing: baseInbound.sniffing.toString(),
|
||||||
};
|
};
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/api/inbounds/add', data, inModal);
|
||||||
},
|
},
|
||||||
openAddInbound() {
|
openAddInbound() {
|
||||||
inModal.show({
|
inModal.show({
|
||||||
|
|
@ -1156,7 +1156,7 @@
|
||||||
}
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/api/inbounds/add', data, inModal);
|
||||||
},
|
},
|
||||||
async updateInbound(inbound, dbInbound) {
|
async updateInbound(inbound, dbInbound) {
|
||||||
const data = {
|
const data = {
|
||||||
|
|
@ -1180,7 +1180,7 @@
|
||||||
}
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/api/inbounds/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
openAddClient(dbInboundId) {
|
openAddClient(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
|
@ -1235,14 +1235,14 @@
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + clients.toString() + ']}',
|
settings: '{"clients": [' + clients.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/addClient`, data, modal);
|
await this.submit(`/panel/api/inbounds/addClient`, data, modal);
|
||||||
},
|
},
|
||||||
async updateClient(client, dbInboundId, clientId) {
|
async updateClient(client, dbInboundId, clientId) {
|
||||||
const data = {
|
const data = {
|
||||||
id: dbInboundId,
|
id: dbInboundId,
|
||||||
settings: '{"clients": [' + client.toString() + ']}',
|
settings: '{"clients": [' + client.toString() + ']}',
|
||||||
};
|
};
|
||||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
await this.submit(`/panel/api/inbounds/updateClient/${clientId}`, data, clientModal);
|
||||||
},
|
},
|
||||||
resetTraffic(dbInboundId) {
|
resetTraffic(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
|
@ -1267,7 +1267,7 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/api/inbounds/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client,confirmation = true) {
|
delClient(dbInboundId, client,confirmation = true) {
|
||||||
|
|
@ -1280,10 +1280,10 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
onOk: () => this.submit(`/panel/api/inbounds/${dbInboundId}/delClient/${clientId}`),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
this.submit(`/panel/api/inbounds/${dbInboundId}/delClient/${clientId}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSubGroupClients(dbInbounds, currentClient) {
|
getSubGroupClients(dbInbounds, currentClient) {
|
||||||
|
|
@ -1362,7 +1362,7 @@
|
||||||
switchEnable(dbInboundId,state) {
|
switchEnable(dbInboundId,state) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
dbInbound.enable = state;
|
dbInbound.enable = state;
|
||||||
this.submit(`/panel/inbound/update/${dbInboundId}`, dbInbound);
|
this.submit(`/panel/api/inbounds/update/${dbInboundId}`, dbInbound);
|
||||||
},
|
},
|
||||||
async switchEnableClient(dbInboundId, client) {
|
async switchEnableClient(dbInboundId, client) {
|
||||||
this.loading()
|
this.loading()
|
||||||
|
|
@ -1392,10 +1392,10 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
onOk: () => this.submit('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
this.submit('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
|
|
@ -1405,7 +1405,7 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/panel/api/inbounds/resetAllTraffics'),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetAllClientTraffics(dbInboundId) {
|
resetAllClientTraffics(dbInboundId) {
|
||||||
|
|
@ -1415,7 +1415,7 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/panel/api/inbounds/resetAllClientTraffics/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
delDepletedClients(dbInboundId) {
|
delDepletedClients(dbInboundId) {
|
||||||
|
|
@ -1425,7 +1425,7 @@
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/panel/api/inbounds/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
|
|
@ -1551,7 +1551,7 @@
|
||||||
value: '',
|
value: '',
|
||||||
okText: '{{ i18n "pages.inbounds.import" }}',
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
confirm: async (dbInboundText) => {
|
confirm: async (dbInboundText) => {
|
||||||
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
await this.submit('/panel/api/inbounds/import', {data: dbInboundText}, promptModal);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -746,7 +746,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
const msg = await HttpUtil.get('/panel/api/server/status');
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
if (!this.loadingStates.fetched) {
|
if (!this.loadingStates.fetched) {
|
||||||
this.loadingStates.fetched = true;
|
this.loadingStates.fetched = true;
|
||||||
|
|
@ -763,7 +763,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async openSelectV2rayVersion() {
|
async openSelectV2rayVersion() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getXrayVersion');
|
const msg = await HttpUtil.get('/panel/api/server/getXrayVersion');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -780,7 +780,7 @@ ${dateTime}
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
await HttpUtil.post(`/server/installXray/${version}`);
|
await HttpUtil.post(`/panel/api/server/installXray/${version}`);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -798,9 +798,9 @@ ${dateTime}
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
const url = isSingleFile
|
const url = isSingleFile
|
||||||
? `/server/updateGeofile/${fileName}`
|
? `/panel/api/server/updateGeofile/${fileName}`
|
||||||
: `/server/updateGeofile`;
|
: `/panel/api/server/updateGeofile`;
|
||||||
await HttpUtil.post(url);
|
await HttpUtil.post(url);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
|
|
@ -808,7 +808,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async stopXrayService() {
|
async stopXrayService() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/stopXrayService');
|
const msg = await HttpUtil.post('/panel/api/server/stopXrayService');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -816,7 +816,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async restartXrayService() {
|
async restartXrayService() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/restartXrayService');
|
const msg = await HttpUtil.post('/panel/api/server/restartXrayService');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -824,7 +824,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async openLogs(){
|
async openLogs(){
|
||||||
logModal.loading = true;
|
logModal.loading = true;
|
||||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
const msg = await HttpUtil.post('/panel/api/server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -834,7 +834,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async openXrayLogs(){
|
async openXrayLogs(){
|
||||||
xraylogModal.loading = true;
|
xraylogModal.loading = true;
|
||||||
const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy});
|
const msg = await HttpUtil.post('/panel/api/server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy});
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -844,7 +844,7 @@ ${dateTime}
|
||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.get('/panel/api/server/getConfigJson');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -855,7 +855,7 @@ ${dateTime}
|
||||||
backupModal.show();
|
backupModal.show();
|
||||||
},
|
},
|
||||||
exportDatabase() {
|
exportDatabase() {
|
||||||
window.location = basePath + 'server/getDb';
|
window.location = basePath + 'panel/api/server/getDb';
|
||||||
},
|
},
|
||||||
importDatabase() {
|
importDatabase() {
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input');
|
||||||
|
|
@ -868,7 +868,7 @@ ${dateTime}
|
||||||
formData.append('db', dbFile);
|
formData.append('db', dbFile);
|
||||||
backupModal.hide();
|
backupModal.hide();
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
|
const uploadMsg = await HttpUtil.post('/panel/api/server/importDB', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
'Content-Type': 'multipart/form-data',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getDBClientIps(email) {
|
async getDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
const msg = await HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
document.getElementById("clientIPs").value = msg.obj;
|
document.getElementById("clientIPs").value = msg.obj;
|
||||||
return;
|
return;
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post(`/panel/inbound/clearClientIps/${email}`);
|
const msg = await HttpUtil.post(`/panel/api/inbounds/clearClientIps/${email}`);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
iconElement.disabled = true;
|
iconElement.disabled = true;
|
||||||
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
const msg = await HttpUtil.postWithModal('/panel/api/inbounds/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.clientModal.clientStats.up = 0;
|
this.clientModal.clientStats.up = 0;
|
||||||
this.clientModal.clientStats.down = 0;
|
this.clientModal.clientStats.down = 0;
|
||||||
|
|
|
||||||
|
|
@ -360,7 +360,7 @@
|
||||||
<code>[[ link.link ]]</code>
|
<code>[[ link.link ]]</code>
|
||||||
</tr-info-row>
|
</tr-info-row>
|
||||||
</template>
|
</template>
|
||||||
<table v-if="inbound.protocol == Protocols.DOKODEMO" class="tr-info-table">
|
<table v-if="inbound.protocol == Protocols.TUNNEL" class="tr-info-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||||
|
|
@ -382,7 +382,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="dbInbound.isSocks" class="tr-info-table">
|
<table v-if="dbInbound.isMixed" class="tr-info-table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "password" }} Auth</th>
|
<th>{{ i18n "password" }} Auth</th>
|
||||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||||
|
|
@ -498,7 +498,7 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
function refreshIPs(email) {
|
function refreshIPs(email) {
|
||||||
return HttpUtil.post(`/panel/inbound/clientIps/${email}`).then((msg) => {
|
return HttpUtil.post(`/panel/api/inbounds/clientIps/${email}`).then((msg) => {
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(msg.obj).join(', ');
|
return JSON.parse(msg.obj).join(', ');
|
||||||
|
|
@ -619,7 +619,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearClientIps() {
|
clearClientIps() {
|
||||||
HttpUtil.post(`/panel/inbound/clearClientIps/${this.infoModal.clientStats.email}`)
|
HttpUtil.post(`/panel/api/inbounds/clearClientIps/${this.infoModal.clientStats.email}`)
|
||||||
.then((msg) => {
|
.then((msg) => {
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
},
|
},
|
||||||
async getNewX25519Cert() {
|
async getNewX25519Cert() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -140,9 +140,13 @@
|
||||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||||
},
|
},
|
||||||
|
clearX25519Cert() {
|
||||||
|
this.inbound.stream.reality.privateKey = '';
|
||||||
|
this.inbound.stream.reality.settings.publicKey = '';
|
||||||
|
},
|
||||||
async getNewmldsa65() {
|
async getNewmldsa65() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewmldsa65');
|
const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -150,9 +154,13 @@
|
||||||
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
||||||
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
||||||
},
|
},
|
||||||
|
clearMldsa65() {
|
||||||
|
this.inbound.stream.reality.mldsa65Seed = '';
|
||||||
|
this.inbound.stream.reality.settings.mldsa65Verify = '';
|
||||||
|
},
|
||||||
async getNewEchCert() {
|
async getNewEchCert() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni });
|
const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni });
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -160,9 +168,13 @@
|
||||||
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
|
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
|
||||||
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
|
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
|
||||||
},
|
},
|
||||||
|
clearEchCert() {
|
||||||
|
this.inbound.stream.tls.echServerKeys = '';
|
||||||
|
this.inbound.stream.tls.settings.echConfigList = '';
|
||||||
|
},
|
||||||
async getNewVlessEnc() {
|
async getNewVlessEnc() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewVlessEnc');
|
const msg = await HttpUtil.get('/panel/api/server/getNewVlessEnc');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
|
@ -181,7 +193,7 @@
|
||||||
inModal.inbound.settings.decryption = block.decryption;
|
inModal.inbound.settings.decryption = block.decryption;
|
||||||
inModal.inbound.settings.encryption = block.encryption;
|
inModal.inbound.settings.encryption = block.encryption;
|
||||||
},
|
},
|
||||||
clearKeys() {
|
clearVlessEnc() {
|
||||||
this.inbound.settings.decryption = 'none';
|
this.inbound.settings.decryption = 'none';
|
||||||
this.inbound.settings.encryption = 'none';
|
this.inbound.settings.encryption = 'none';
|
||||||
this.inbound.settings.selectedAuth = undefined;
|
this.inbound.settings.selectedAuth = undefined;
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@
|
||||||
methods: {
|
methods: {
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
try {
|
try {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
const msg = await HttpUtil.get('/panel/api/server/status');
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.serverStatus = msg.obj;
|
this.serverStatus = msg.obj;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -420,7 +420,7 @@
|
||||||
},
|
},
|
||||||
async restartXray() {
|
async restartXray() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("server/restartXrayService");
|
const msg = await HttpUtil.post("/panel/api/server/restartXrayService");
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
await PromiseUtil.sleep(500);
|
await PromiseUtil.sleep(500);
|
||||||
|
|
@ -572,7 +572,7 @@
|
||||||
serverObj = o.settings.vnext;
|
serverObj = o.settings.vnext;
|
||||||
break;
|
break;
|
||||||
case Protocols.HTTP:
|
case Protocols.HTTP:
|
||||||
case Protocols.Socks:
|
case Protocols.Mixed:
|
||||||
case Protocols.Shadowsocks:
|
case Protocols.Shadowsocks:
|
||||||
case Protocols.Trojan:
|
case Protocols.Trojan:
|
||||||
serverObj = o.settings.servers;
|
serverObj = o.settings.servers;
|
||||||
|
|
|
||||||
42
web/job/periodic_client_traffic_reset_job.go
Normal file
42
web/job/periodic_client_traffic_reset_job.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PeriodicClientTrafficResetJob struct {
|
||||||
|
inboundService service.InboundService
|
||||||
|
period Period
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPeriodicClientTrafficResetJob(period Period) *PeriodicClientTrafficResetJob {
|
||||||
|
return &PeriodicClientTrafficResetJob{
|
||||||
|
period: period,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *PeriodicClientTrafficResetJob) Run() {
|
||||||
|
clients, err := j.inboundService.GetClientsByTrafficReset(string(j.period))
|
||||||
|
logger.Infof("Running periodic client traffic reset job for period: %s", j.period)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get clients for traffic reset:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCount := 0
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if err := j.inboundService.ResetClientTrafficByEmail(client.Email); err != nil {
|
||||||
|
logger.Warning("Failed to reset traffic for client", client.Email, ":", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCount++
|
||||||
|
logger.Infof("Reset traffic for client %s", client.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resetCount > 0 {
|
||||||
|
logger.Infof("Periodic client traffic reset completed: %d clients reseted", resetCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
@ -20,7 +19,7 @@ func NewPeriodicTrafficResetJob(period Period) *PeriodicTrafficResetJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *PeriodicTrafficResetJob) Run() {
|
func (j *PeriodicTrafficResetJob) Run() {
|
||||||
inbounds, err := j.inboundService.GetInboundsByPeriodicTrafficReset(string(j.period))
|
inbounds, err := j.inboundService.GetInboundsByTrafficReset(string(j.period))
|
||||||
logger.Infof("Running periodic traffic reset job for period: %s", j.period)
|
logger.Infof("Running periodic traffic reset job for period: %s", j.period)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Failed to get inbounds for traffic reset:", err)
|
logger.Warning("Failed to get inbounds for traffic reset:", err)
|
||||||
|
|
@ -43,7 +42,3 @@ func (j *PeriodicTrafficResetJob) Run() {
|
||||||
logger.Infof("Periodic traffic reset completed: %d inbounds reseted", resetCount)
|
logger.Infof("Periodic traffic reset completed: %d inbounds reseted", resetCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *PeriodicTrafficResetJob) getResetKey(inbound *model.Inbound) string {
|
|
||||||
return inbound.PeriodicTrafficReset + "_" + inbound.Tag
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "tunnel",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,46 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetInboundsByPeriodicTrafficReset(period string) ([]*model.Inbound, error) {
|
func (s *InboundService) GetInboundsByTrafficReset(period string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("periodic_traffic_reset = ?", period).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("traffic_reset = ?", period).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientsByTrafficReset(period string) ([]model.Client, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
|
||||||
|
// Get all inbounds first
|
||||||
|
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientsWithReset []model.Client
|
||||||
|
|
||||||
|
// Parse each inbound's settings to find clients with matching traffic reset period
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
clients, err := s.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get clients for inbound", inbound.Id, ":", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.TrafficReset == period {
|
||||||
|
clientsWithReset = append(clientsWithReset, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientsWithReset, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
|
func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
||||||
|
|
@ -419,7 +449,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||||
oldInbound.Remark = inbound.Remark
|
oldInbound.Remark = inbound.Remark
|
||||||
oldInbound.Enable = inbound.Enable
|
oldInbound.Enable = inbound.Enable
|
||||||
oldInbound.ExpiryTime = inbound.ExpiryTime
|
oldInbound.ExpiryTime = inbound.ExpiryTime
|
||||||
oldInbound.PeriodicTrafficReset = inbound.PeriodicTrafficReset
|
oldInbound.TrafficReset = inbound.TrafficReset
|
||||||
oldInbound.Listen = inbound.Listen
|
oldInbound.Listen = inbound.Listen
|
||||||
oldInbound.Port = inbound.Port
|
oldInbound.Port = inbound.Port
|
||||||
oldInbound.Protocol = inbound.Protocol
|
oldInbound.Protocol = inbound.Protocol
|
||||||
|
|
@ -709,6 +739,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
|
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) (bool, error) {
|
||||||
|
// TODO: check if TrafficReset field are updating
|
||||||
clients, err := s.GetClients(data)
|
clients, err := s.GetClients(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -1695,6 +1726,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
||||||
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
// Reset traffic stats in ClientTraffic table
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
result := db.Model(xray.ClientTraffic{}).
|
||||||
Where("email = ?", clientEmail).
|
Where("email = ?", clientEmail).
|
||||||
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||||
|
|
@ -1703,6 +1735,48 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update lastTrafficResetTime in client settings
|
||||||
|
_, inbound, err := s.GetClientInboundByEmail(clientEmail)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get inbound for client", clientEmail, ":", err)
|
||||||
|
return nil // Don't fail the reset if we can't update the timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
if inbound != nil {
|
||||||
|
var settings map[string]any
|
||||||
|
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to parse inbound settings:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clientsSettings := settings["clients"].([]any)
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
|
||||||
|
for client_index := range clientsSettings {
|
||||||
|
c := clientsSettings[client_index].(map[string]any)
|
||||||
|
if c["email"] == clientEmail {
|
||||||
|
c["lastTrafficResetTime"] = now
|
||||||
|
c["updated_at"] = now
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings["clients"] = clientsSettings
|
||||||
|
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to marshal inbound settings:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound.Settings = string(modifiedSettings)
|
||||||
|
err = db.Save(inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to save inbound with updated client settings:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"x-ui/util/sys"
|
"x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
"github.com/shirou/gopsutil/v4/host"
|
"github.com/shirou/gopsutil/v4/host"
|
||||||
|
|
@ -872,12 +873,6 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthBlock struct {
|
|
||||||
Label string `json:"label"`
|
|
||||||
Decryption string `json:"decryption"`
|
|
||||||
Encryption string `json:"encryption"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerService) GetNewVlessEnc() (any, error) {
|
func (s *ServerService) GetNewVlessEnc() (any, error) {
|
||||||
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
|
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
|
@ -887,37 +882,70 @@ func (s *ServerService) GetNewVlessEnc() (any, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(out.String(), "\n")
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
var auths []map[string]string
|
||||||
var blocks []AuthBlock
|
var current map[string]string
|
||||||
var current *AuthBlock
|
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(line, "Authentication:") {
|
if strings.HasPrefix(line, "Authentication:") {
|
||||||
if current != nil {
|
if current != nil {
|
||||||
blocks = append(blocks, *current)
|
auths = append(auths, current)
|
||||||
|
}
|
||||||
|
current = map[string]string{
|
||||||
|
"label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
|
||||||
}
|
}
|
||||||
current = &AuthBlock{Label: strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))}
|
|
||||||
} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
|
} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
|
||||||
parts := strings.SplitN(line, ":", 2)
|
parts := strings.SplitN(line, ":", 2)
|
||||||
if len(parts) == 2 && current != nil {
|
if len(parts) == 2 && current != nil {
|
||||||
key := strings.Trim(parts[0], `" `)
|
key := strings.Trim(parts[0], `" `)
|
||||||
val := strings.Trim(parts[1], `" `)
|
val := strings.Trim(parts[1], `" `)
|
||||||
switch key {
|
current[key] = val
|
||||||
case "decryption":
|
|
||||||
current.Decryption = val
|
|
||||||
case "encryption":
|
|
||||||
current.Encryption = val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if current != nil {
|
if current != nil {
|
||||||
blocks = append(blocks, *current)
|
auths = append(auths, current)
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"auths": blocks,
|
"auths": auths,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewUUID() (map[string]string, error) {
|
||||||
|
newUUID, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]string{
|
||||||
|
"uuid": newUUID.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewmlkem768() (any, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "mlkem768")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
SeedLine := strings.Split(lines[0], ":")
|
||||||
|
ClientLine := strings.Split(lines[1], ":")
|
||||||
|
|
||||||
|
seed := strings.TrimSpace(SeedLine[1])
|
||||||
|
client := strings.TrimSpace(ClientLine[1])
|
||||||
|
|
||||||
|
keyPair := map[string]any{
|
||||||
|
"seed": seed,
|
||||||
|
"client": client,
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2129,8 +2129,8 @@ func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
excludedProtocols := map[model.Protocol]bool{
|
excludedProtocols := map[model.Protocol]bool{
|
||||||
model.DOKODEMO: true,
|
model.Tunnel: true,
|
||||||
model.Socks: true,
|
model.Mixed: true,
|
||||||
model.WireGuard: true,
|
model.WireGuard: true,
|
||||||
model.HTTP: true,
|
model.HTTP: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
web/web.go
15
web/web.go
|
|
@ -176,7 +176,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"})))
|
engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/api/"})))
|
||||||
assetsBasePath := basePath + "assets/"
|
assetsBasePath := basePath + "assets/"
|
||||||
|
|
||||||
store := cookie.NewStore(secret)
|
store := cookie.NewStore(secret)
|
||||||
|
|
@ -269,13 +269,26 @@ func (s *Server) startTask() {
|
||||||
// Periodic traffic resets
|
// Periodic traffic resets
|
||||||
logger.Info("Scheduling periodic traffic reset jobs")
|
logger.Info("Scheduling periodic traffic reset jobs")
|
||||||
{
|
{
|
||||||
|
// Inbound traffic reset jobs
|
||||||
// Run once a day, midnight
|
// Run once a day, midnight
|
||||||
|
// TODO: for testing, run every minute, change back to daily later
|
||||||
// s.cron.AddJob("@daily", job.NewPeriodicTrafficResetJob("daily"))
|
// s.cron.AddJob("@daily", job.NewPeriodicTrafficResetJob("daily"))
|
||||||
s.cron.AddJob("* * * * *", job.NewPeriodicTrafficResetJob("daily"))
|
s.cron.AddJob("* * * * *", job.NewPeriodicTrafficResetJob("daily"))
|
||||||
// Run once a week, midnight between Sat/Sun
|
// Run once a week, midnight between Sat/Sun
|
||||||
s.cron.AddJob("@weekly", job.NewPeriodicTrafficResetJob("weekly"))
|
s.cron.AddJob("@weekly", job.NewPeriodicTrafficResetJob("weekly"))
|
||||||
// Run once a month, midnight, first of month
|
// Run once a month, midnight, first of month
|
||||||
s.cron.AddJob("@monthly", job.NewPeriodicTrafficResetJob("monthly"))
|
s.cron.AddJob("@monthly", job.NewPeriodicTrafficResetJob("monthly"))
|
||||||
|
|
||||||
|
// Client traffic reset jobs
|
||||||
|
logger.Info("Scheduling periodic client traffic reset jobs")
|
||||||
|
// Run once a day, midnight
|
||||||
|
// TODO: for testing, run every minute, change back to daily later
|
||||||
|
// s.cron.AddJob("@daily", job.NewPeriodicClientTrafficResetJob("daily"))
|
||||||
|
s.cron.AddJob("* * * * *", job.NewPeriodicClientTrafficResetJob("daily"))
|
||||||
|
// Run once a week, midnight between Sat/Sun
|
||||||
|
s.cron.AddJob("@weekly", job.NewPeriodicClientTrafficResetJob("weekly"))
|
||||||
|
// Run once a month, midnight, first of month
|
||||||
|
s.cron.AddJob("@monthly", job.NewPeriodicClientTrafficResetJob("monthly"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a traffic condition every day, 8:30
|
// Make a traffic condition every day, 8:30
|
||||||
|
|
|
||||||
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
Binary file not shown.
13
windows_files/readme.txt
Normal file
13
windows_files/readme.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
you can't install fail2ban on windows
|
||||||
|
we don't have bash menu for windows
|
||||||
|
if you forgot your password you need to check your database with https://sqlitebrowser.org/
|
||||||
|
the app need to be open all the time
|
||||||
|
|
||||||
|
default setting:
|
||||||
|
http://localhost:2053/
|
||||||
|
user: admin
|
||||||
|
pass: admin
|
||||||
|
port: 2053
|
||||||
|
|
||||||
|
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt
|
||||||
Loading…
Reference in a new issue