Compare commits

..

21 Commits

Author SHA1 Message Date
mhsanaei
2750f46c01 v2.6.7 2025-08-30 16:05:33 +02:00
mhsanaei
023eb513e4 Xray Core v25.8.29 2025-08-30 10:03:32 +02:00
mhsanaei
0c7b59ed47 removed: Allocate 2025-08-28 10:15:04 +02:00
Ali Golzar
3087c1b123 Add all-time traffic for inbounds and clients (#3387)
* feat(db): add allTime field to Inbound and ClientTraffic models

* feat(inbound): increment all_time for inbounds and clients on traffic updates

calculate correct all_time traffic on migrate command

* feat(ui): show all-time traffic column for inbounds and its clients

* i18n: add pages.inbounds.allTimeTraffic label across locales

* Add All Time Traffic Usage in inbounds page top banner
2025-08-28 01:10:50 +02:00
Ali Golzar
2198397197 Created / Updated fields for clients (#3384)
* feat(backend): add created_at/updated_at to clients and maintain on create/update
backfill existing clients and set updated_at on mutations

* feat(frontend): carry created_at/updated_at in client models and round-trip via JSON

* feat(frontend): display Created and Updated columns in client table with proper date formatting

* i18n: add pages.inbounds.createdAt/updatedAt across all locales

* Update inbound.go

Remove duplicate code
2025-08-27 19:30:49 +02:00
Igor Finagin
d10c312e62 AutoFill OTP (#3381)
https://developer.apple.com/documentation/security/enabling-password-autofill-on-an-html-input-element
2025-08-25 13:42:02 +02:00
mhsanaei
24a3411465 more list for public IP address 2025-08-21 14:24:25 +02:00
Alireza Ahmand
2198e7a28f feat: Add remaining time to tgbot #3355 (#3360) 2025-08-17 13:43:25 +02:00
mhsanaei
6b23b416a7 minor changes 2025-08-17 13:37:49 +02:00
mhsanaei
16f53ce4c2 go v1.25 2025-08-17 12:27:21 +02:00
mhsanaei
27445b30e9 DNS outbound: Set "reject" as the default value for nonIPQuery 2025-08-17 12:22:33 +02:00
mhsanaei
3d0212c21d fix: fail2ban on Debian 12 #1701 2025-08-15 13:34:02 +02:00
mhsanaei
978755960f actions/checkout from 4 to 5 2025-08-14 18:41:53 +02:00
mhsanaei
9b51e9a5c5 Freedom: Add maxSplit fragment option; Add applyTo noises option 2025-08-14 18:38:56 +02:00
fgsfds
6879a8fbcb Moved DB to same app folder on Windows (#3340)
* moved db to user folder on windows

* moved db to local appdata

* made getDBFolderPath func private

* added getWindowsDbPath() func

* fix

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-08-13 23:19:59 +02:00
mhsanaei
7258841491 Update FUNDING.yml 2025-08-12 13:00:16 +02:00
mhsanaei
23dd80fbb0 remove unnecessary ant files 2025-08-12 12:57:02 +02:00
mhsanaei
6556884c7f remove unnecessary vue files 2025-08-12 12:56:49 +02:00
Alireza Ahmadi
d5c532c64f fix saving sockopt 2025-08-09 16:07:33 +02:00
mhsanaei
ad5f774a1e Axios v1.11.0 2025-08-09 13:46:28 +02:00
g0l4
aa285914fa chore: update polygon token name (#3338) 2025-08-09 08:18:56 +02:00
69 changed files with 864 additions and 74329 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: MHSanaei
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true

View File

@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
@@ -84,7 +84,7 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.8.3/" Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.8.29/"
if [ "${{ matrix.platform }}" == "amd64" ]; then if [ "${{ matrix.platform }}" == "amd64" ]; then
wget -q ${Xray_URL}Xray-linux-64.zip wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip

4
.gitignore vendored
View File

@@ -29,9 +29,9 @@ main
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Ignore Go specific files # Ignore Go build files
*.exe *.exe
*.exe~ x-ui.db
# Ignore Docker specific files # Ignore Docker specific files
docker-compose.override.yml docker-compose.override.yml

View File

@@ -27,7 +27,7 @@ case $1 in
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.8.3/Xray-linux-${ARCH}.zip" wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.8.29/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"

View File

@@ -1,7 +1,7 @@
# ======================================================== # ========================================================
# Stage: Builder # Stage: Builder
# ======================================================== # ========================================================
FROM golang:1.24-alpine AS builder FROM golang:1.25-alpine AS builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH

View File

@@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## النجوم عبر الزمن ## النجوم عبر الزمن

View File

@@ -48,7 +48,7 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Estrellas a lo Largo del Tiempo ## Estrellas a lo Largo del Tiempo

View File

@@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## ستاره‌ها در طول زمان ## ستاره‌ها در طول زمان

View File

@@ -48,7 +48,7 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Stargazers over Time ## Stargazers over Time

View File

@@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## Звезды с течением времени ## Звезды с течением времени

View File

@@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
</p> </p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC` - USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
## 随时间变化的星标数 ## 随时间变化的星标数

View File

@@ -3,7 +3,10 @@ package config
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath"
"runtime"
"strings" "strings"
) )
@@ -54,12 +57,32 @@ func GetBinFolderPath() string {
return binFolderPath return binFolderPath
} }
func getBaseDir() string {
exePath, err := os.Executable()
if err != nil {
return "."
}
exeDir := filepath.Dir(exePath)
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
wd, err := os.Getwd()
if err != nil {
return "."
}
return wd
}
return exeDir
}
func GetDBFolderPath() string { func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER") dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" { if dbFolderPath != "" {
dbFolderPath = "/etc/x-ui" return dbFolderPath
} }
return dbFolderPath if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/etc/x-ui"
} }
func GetDBPath() string { func GetDBPath() string {
@@ -68,8 +91,54 @@ func GetDBPath() string {
func GetLogFolder() string { func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER") logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath == "" { if logFolderPath != "" {
logFolderPath = "/var/log" return logFolderPath
} }
return logFolderPath if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/var/log"
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Sync()
}
func init() {
if runtime.GOOS != "windows" {
return
}
if os.Getenv("XUI_DB_FOLDER") != "" {
return
}
oldDBFolder := "/etc/x-ui"
oldDBPath := fmt.Sprintf("%s/%s.db", oldDBFolder, GetName())
newDBFolder := GetDBFolderPath()
newDBPath := fmt.Sprintf("%s/%s.db", newDBFolder, GetName())
_, err := os.Stat(newDBPath)
if err == nil {
return // new exists
}
_, err = os.Stat(oldDBPath)
if os.IsNotExist(err) {
return // old does not exist
}
_ = copyFile(oldDBPath, newDBPath) // ignore error
} }

View File

@@ -1 +1 @@
2.6.6 2.6.7

View File

@@ -32,6 +32,7 @@ type Inbound struct {
Up int64 `json:"up" form:"up"` Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"` Down int64 `json:"down" form:"down"`
Total int64 `json:"total" form:"total"` Total int64 `json:"total" form:"total"`
AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"`
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"`
@@ -45,7 +46,6 @@ type Inbound struct {
StreamSettings string `json:"streamSettings" form:"streamSettings"` StreamSettings string `json:"streamSettings" form:"streamSettings"`
Tag string `json:"tag" form:"tag" gorm:"unique"` Tag string `json:"tag" form:"tag" gorm:"unique"`
Sniffing string `json:"sniffing" form:"sniffing"` Sniffing string `json:"sniffing" form:"sniffing"`
Allocate string `json:"allocate" form:"allocate"`
} }
type OutboundTraffics struct { type OutboundTraffics struct {
@@ -80,7 +80,6 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
StreamSettings: json_util.RawMessage(i.StreamSettings), StreamSettings: json_util.RawMessage(i.StreamSettings),
Tag: i.Tag, Tag: i.Tag,
Sniffing: json_util.RawMessage(i.Sniffing), Sniffing: json_util.RawMessage(i.Sniffing),
Allocate: json_util.RawMessage(i.Allocate),
} }
} }
@@ -104,4 +103,6 @@ type Client struct {
SubID string `json:"subId" form:"subId"` SubID string `json:"subId" form:"subId"`
Comment string `json:"comment" form:"comment"` Comment string `json:"comment" form:"comment"`
Reset int `json:"reset" form:"reset"` Reset int `json:"reset" form:"reset"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
} }

26
go.mod
View File

@@ -1,6 +1,6 @@
module x-ui module x-ui
go 1.24.5 go 1.25.0
require ( require (
github.com/gin-contrib/gzip v1.2.3 github.com/gin-contrib/gzip v1.2.3
@@ -9,21 +9,21 @@ require (
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mymmrac/telego v1.2.0 github.com/mymmrac/telego v1.3.0
github.com/nicksnyder/go-i18n/v2 v2.6.0 github.com/nicksnyder/go-i18n/v2 v2.6.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.7 github.com/shirou/gopsutil/v4 v4.25.7
github.com/valyala/fasthttp v1.64.0 github.com/valyala/fasthttp v1.65.0
github.com/xlzd/gotp v0.1.0 github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.250803.0 github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0
golang.org/x/text v0.28.0 golang.org/x/text v0.28.0
google.golang.org/grpc v1.74.2 google.golang.org/grpc v1.75.0
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.1 gorm.io/gorm v1.30.2
) )
require ( require (
@@ -34,7 +34,7 @@ require (
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.8.4 // indirect github.com/ebitengine/purego v0.8.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
@@ -54,9 +54,9 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.30 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/miekg/dns v1.1.68 // indirect github.com/miekg/dns v1.1.68 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -79,9 +79,9 @@ require (
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a // indirect github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.2 // indirect go.uber.org/mock v0.6.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.27.0 // indirect
@@ -92,8 +92,8 @@ require (
golang.org/x/tools v0.36.0 // indirect golang.org/x/tools v0.36.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/protobuf v1.36.7 // indirect google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
lukechampine.com/blake3 v1.4.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect

74
go.sum
View File

@@ -19,8 +19,8 @@ github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mT
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U= github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
@@ -91,12 +91,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -104,8 +104,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v1.2.0 h1:CHmR9eiugpTiF/ttppmK89E6mcu9d4DmNQS0tMpUs6M= github.com/mymmrac/telego v1.3.0 h1:y2bDDCioLgkcs+5luUaPgTNHKel1Qh30iUxFcMUrowg=
github.com/mymmrac/telego v1.2.0/go.mod h1:OiCm4QjqB/ZY2E4VAmkVH8EeLLhM4QuFuO1KOCuvGoM= github.com/mymmrac/telego v1.3.0/go.mod h1:0D2l/IA/gUFn4oqsi1O4/tSnlezw5jNV/ReFRDUEKk8=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ= github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE= github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@@ -148,8 +148,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
@@ -162,8 +162,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA= github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
@@ -172,30 +172,30 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a h1:Fs8Pc0JAc/LDOf9Q4DzKrk+Ujf4ILlyvfvDVZcmOZ2o= github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f h1:o1Kryl9qEYYzNep9RId9DM1kBn8tBrcK5UJnti/l0NI=
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0= github.com/xtls/reality v0.0.0-20250828044527-046fad5ab64f/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
github.com/xtls/xray-core v1.250803.0 h1:sYdRC243UsujnePINH4IfM4MfHE4lj2p4wZFAfeE2GI= github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5 h1:rBqCVgic8yIUVHB4h26K8JNuwJuNj45egsdXxwEvA7E=
github.com/xtls/xray-core v1.250803.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE= github.com/xtls/xray-core v1.250803.1-0.20250829143322-81b7cd718ad5/go.mod h1:WB/73DmN9Vs7lxtx4Xc/D0Ub1VUu06hAh1mMh8JN2uM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
@@ -226,12 +226,14 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -243,8 +245,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=

View File

@@ -7,7 +7,6 @@ yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
cur_dir=$(pwd) cur_dir=$(pwd)
show_ip_service_lists=("https://api.ipify.org" "https://4.ident.me")
# check root # check root
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
@@ -58,7 +57,7 @@ install_base() {
zypper refresh && zypper -q install -y wget curl tar timezone zypper refresh && zypper -q install -y wget curl tar timezone
;; ;;
*) *)
apt-get update && apt install -y -q wget curl tar tzdata apt-get update && apt-get install -y -q wget curl tar tzdata
;; ;;
esac esac
} }
@@ -73,10 +72,18 @@ config_after_install() {
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}') local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local URL_lists=(
for ip_service_addr in "${show_ip_service_lists[@]}"; do "https://api4.ipify.org"
local server_ip=$(curl -s --max-time 3 ${ip_service_addr} 2>/dev/null) "https://ipv4.icanhazip.com"
if [ -n "${server_ip}" ]; then "https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip=""
for ip_address in "${URL_lists[@]}"; do
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
if [[ -n "${server_ip}" ]]; then
break break
fi fi
done done

View File

@@ -209,9 +209,10 @@ func (s *SubJsonService) streamData(stream string) map[string]any {
var streamSettings map[string]any var streamSettings map[string]any
json.Unmarshal([]byte(stream), &streamSettings) json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string) security, _ := streamSettings["security"].(string)
if security == "tls" { switch security {
case "tls":
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any)) streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
} else if security == "reality" { case "reality":
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any)) streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
} }
delete(streamSettings, "sockopt") delete(streamSettings, "sockopt")

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
@import "../lib/style/index.less";
@import "../lib/style/components.less";
@green-6: #008771;
@primary-color: @green-6;
@border-radius-base: 1rem;
@progress-remaining-color: #EDEDED;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ class DBInbound {
this.up = 0; this.up = 0;
this.down = 0; this.down = 0;
this.total = 0; this.total = 0;
this.allTime = 0;
this.remark = ""; this.remark = "";
this.enable = true; this.enable = true;
this.expiryTime = 0; this.expiryTime = 0;

View File

@@ -1042,27 +1042,6 @@ class Sniffing extends XrayCommonClass {
} }
} }
class Allocate extends XrayCommonClass {
constructor(
strategy = "always",
refresh = 5,
concurrency = 3,
) {
super();
this.strategy = strategy;
this.refresh = refresh;
this.concurrency = concurrency;
}
static fromJson(json = {}) {
return new Allocate(
json.strategy,
json.refresh,
json.concurrency,
);
}
}
class Inbound extends XrayCommonClass { class Inbound extends XrayCommonClass {
constructor( constructor(
port = RandomUtil.randomInteger(10000, 60000), port = RandomUtil.randomInteger(10000, 60000),
@@ -1072,7 +1051,6 @@ class Inbound extends XrayCommonClass {
streamSettings = new StreamSettings(), streamSettings = new StreamSettings(),
tag = '', tag = '',
sniffing = new Sniffing(), sniffing = new Sniffing(),
allocate = new Allocate(),
clientStats = '', clientStats = '',
) { ) {
super(); super();
@@ -1083,7 +1061,6 @@ class Inbound extends XrayCommonClass {
this.stream = streamSettings; this.stream = streamSettings;
this.tag = tag; this.tag = tag;
this.sniffing = sniffing; this.sniffing = sniffing;
this.allocate = allocate;
this.clientStats = clientStats; this.clientStats = clientStats;
} }
getClientStats() { getClientStats() {
@@ -1248,7 +1225,6 @@ class Inbound extends XrayCommonClass {
this.stream = new StreamSettings(); this.stream = new StreamSettings();
this.tag = ''; this.tag = '';
this.sniffing = new Sniffing(); this.sniffing = new Sniffing();
this.allocate = new Allocate();
} }
genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security) { genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security) {
@@ -1703,14 +1679,13 @@ class Inbound extends XrayCommonClass {
StreamSettings.fromJson(json.streamSettings), StreamSettings.fromJson(json.streamSettings),
json.tag, json.tag,
Sniffing.fromJson(json.sniffing), Sniffing.fromJson(json.sniffing),
Allocate.fromJson(json.allocate),
json.clientStats json.clientStats
) )
} }
toJson() { toJson() {
let streamSettings; let streamSettings;
if (this.canEnableStream()) { if (this.canEnableStream() || this.stream?.sockopt) {
streamSettings = this.stream.toJson(); streamSettings = this.stream.toJson();
} }
return { return {
@@ -1721,7 +1696,6 @@ class Inbound extends XrayCommonClass {
streamSettings: streamSettings, streamSettings: streamSettings,
tag: this.tag, tag: this.tag,
sniffing: this.sniffing.toJson(), sniffing: this.sniffing.toJson(),
allocate: this.allocate.toJson(),
clientStats: this.clientStats clientStats: this.clientStats
}; };
} }
@@ -1817,7 +1791,9 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '', comment = '',
reset = 0 reset = 0,
created_at = undefined,
updated_at = undefined
) { ) {
super(); super();
this.id = id; this.id = id;
@@ -1831,6 +1807,8 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
this.subId = subId; this.subId = subId;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at;
this.updated_at = updated_at;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -1846,6 +1824,8 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
json.subId, json.subId,
json.comment, json.comment,
json.reset, json.reset,
json.created_at,
json.updated_at,
); );
} }
get _expiryTime() { get _expiryTime() {
@@ -1926,7 +1906,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '', comment = '',
reset = 0 reset = 0,
created_at = undefined,
updated_at = undefined
) { ) {
super(); super();
this.id = id; this.id = id;
@@ -1940,6 +1922,8 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.subId = subId; this.subId = subId;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at;
this.updated_at = updated_at;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -1955,6 +1939,8 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.subId, json.subId,
json.comment, json.comment,
json.reset, json.reset,
json.created_at,
json.updated_at,
); );
} }
@@ -2065,7 +2051,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '', comment = '',
reset = 0 reset = 0,
created_at = undefined,
updated_at = undefined
) { ) {
super(); super();
this.password = password; this.password = password;
@@ -2078,6 +2066,8 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.subId = subId; this.subId = subId;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at;
this.updated_at = updated_at;
} }
toJson() { toJson() {
@@ -2092,6 +2082,8 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
subId: this.subId, subId: this.subId,
comment: this.comment, comment: this.comment,
reset: this.reset, reset: this.reset,
created_at: this.created_at,
updated_at: this.updated_at,
}; };
} }
@@ -2107,6 +2099,8 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.subId, json.subId,
json.comment, json.comment,
json.reset, json.reset,
json.created_at,
json.updated_at,
); );
} }
@@ -2226,7 +2220,9 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
comment = '', comment = '',
reset = 0 reset = 0,
created_at = undefined,
updated_at = undefined
) { ) {
super(); super();
this.method = method; this.method = method;
@@ -2240,6 +2236,8 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.subId = subId; this.subId = subId;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at;
this.updated_at = updated_at;
} }
toJson() { toJson() {
@@ -2255,6 +2253,8 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
subId: this.subId, subId: this.subId,
comment: this.comment, comment: this.comment,
reset: this.reset, reset: this.reset,
created_at: this.created_at,
updated_at: this.updated_at,
}; };
} }
@@ -2271,6 +2271,8 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.subId, json.subId,
json.comment, json.comment,
json.reset, json.reset,
json.created_at,
json.updated_at,
); );
} }

View File

@@ -919,12 +919,14 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
constructor( constructor(
packets = '1-3', packets = '1-3',
length = '', length = '',
interval = '' interval = '',
maxSplit = ''
) { ) {
super(); super();
this.packets = packets; this.packets = packets;
this.length = length; this.length = length;
this.interval = interval; this.interval = interval;
this.maxSplit = maxSplit;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -932,6 +934,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
json.packets, json.packets,
json.length, json.length,
json.interval, json.interval,
json.maxSplit
); );
} }
}; };
@@ -940,12 +943,14 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
constructor( constructor(
type = 'rand', type = 'rand',
packet = '10-20', packet = '10-20',
delay = '10-16' delay = '10-16',
applyTo = 'ip'
) { ) {
super(); super();
this.type = type; this.type = type;
this.packet = packet; this.packet = packet;
this.delay = delay; this.delay = delay;
this.applyTo = applyTo;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -953,6 +958,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
json.type, json.type,
json.packet, json.packet,
json.delay, json.delay,
json.applyTo
); );
} }
@@ -961,6 +967,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
type: this.type, type: this.type,
packet: this.packet, packet: this.packet,
delay: this.delay, delay: this.delay,
applyTo: this.applyTo
}; };
} }
}; };
@@ -988,7 +995,7 @@ Outbound.DNSSettings = class extends CommonClass {
network = 'udp', network = 'udp',
address = '', address = '',
port = 53, port = 53,
nonIPQuery = 'drop', nonIPQuery = 'reject',
blockTypes = [] blockTypes = []
) { ) {
super(); super();

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
if (process.env.NODE_ENV === 'production') {
module.exports = require('./vue.common.prod.js')
} else {
module.exports = require('./vue.common.dev.js')
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
if (process.env.NODE_ENV === 'production') {
module.exports = require('./vue.runtime.common.prod.js')
} else {
module.exports = require('./vue.runtime.common.dev.js')
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,76 +0,0 @@
import Vue from './vue.runtime.common.js'
export default Vue
// this should be kept in sync with src/v3/index.ts
export const {
version,
// refs
ref,
shallowRef,
isRef,
toRef,
toRefs,
unref,
proxyRefs,
customRef,
triggerRef,
computed,
// reactive
reactive,
isReactive,
isReadonly,
isShallow,
isProxy,
shallowReactive,
markRaw,
toRaw,
readonly,
shallowReadonly,
// watch
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
// effectScope
effectScope,
onScopeDispose,
getCurrentScope,
// provide / inject
provide,
inject,
// lifecycle
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated,
onServerPrefetch,
onRenderTracked,
onRenderTriggered,
// v2 only
set,
del,
// v3 compat
h,
getCurrentInstance,
useSlots,
useAttrs,
mergeDefaults,
nextTick,
useCssModule,
useCssVars,
defineComponent,
defineAsyncComponent
} = Vue

View File

@@ -2,10 +2,10 @@ package entity
import ( import (
"crypto/tls" "crypto/tls"
"math"
"net" "net"
"strings" "strings"
"time" "time"
"math"
"x-ui/util/common" "x-ui/util/common"
) )
@@ -39,8 +39,8 @@ type AllSetting struct {
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"` TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"` TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"` TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
SubEnable bool `json:"subEnable" form:"subEnable"` SubEnable bool `json:"subEnable" form:"subEnable"`
SubTitle string `json:"subTitle" form:"subTitle"` SubTitle string `json:"subTitle" form:"subTitle"`
SubListen string `json:"subListen" form:"subListen"` SubListen string `json:"subListen" form:"subListen"`

View File

@@ -98,6 +98,10 @@
</table> </table>
</a-popover> </a-popover>
</template> </template>
<template slot="allTime" slot-scope="text, client">
<a-tag>[[ SizeFormatter.sizeFormat(getAllTimeClient(record, client.email)) ]]</a-tag>
</template>
<template slot="expiryTime" slot-scope="text, client, index"> <template slot="expiryTime" slot-scope="text, client, index">
<template v-if="client.expiryTime !=0 && client.reset >0"> <template v-if="client.expiryTime !=0 && client.reset >0">
<a-popover :overlay-class-name="themeSwitcher.currentTheme"> <a-popover :overlay-class-name="themeSwitcher.currentTheme">
@@ -278,4 +282,30 @@
</a-badge> </a-badge>
</a-popover> </a-popover>
</template> </template>
<template slot="createdAt" slot-scope="text, client, index">
<template v-if="client.created_at">
<template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(client.created_at) ]]
</template>
<template v-else>
[[ DateUtil.convertToJalalian(moment(client.created_at)) ]]
</template>
</template>
<template v-else>
-
</template>
</template>
<template slot="updatedAt" slot-scope="text, client, index">
<template v-if="client.updated_at">
<template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(client.updated_at) ]]
</template>
<template v-else>
[[ DateUtil.convertToJalalian(moment(client.updated_at)) ]]
</template>
</template>
<template v-else>
-
</template>
</template>
{{end}} {{end}}

View File

@@ -1,15 +0,0 @@
{{define "form/allocate"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='Strategy'>
<a-select v-model="inbound.allocate.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['always','random']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='Refresh'>
<a-input-number v-model.number="inbound.allocate.refresh" min="0"></a-input-number>
</a-form-item>
<a-form-item label='Concurrency'>
<a-input-number v-model.number="inbound.allocate.concurrency" min="0"></a-input-number>
</a-form-item>
</a-form>
{{end}}

View File

@@ -121,13 +121,4 @@
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<!-- allocate -->
<!-- Temporarily disabled until we accepts range for port allocation
<a-collapse>
<a-collapse-panel header='Allocate'>
{{template "form/allocate"}}
</a-collapse-panel>
</a-collapse>
-->
{{end}} {{end}}

View File

@@ -42,6 +42,9 @@
<a-form-item label='Interval'> <a-form-item label='Interval'>
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input> <a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Max Split'>
<a-input v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
</a-form-item>
</template> </template>
<!-- Switch for Noises --> <!-- Switch for Noises -->
@@ -75,6 +78,11 @@
<a-form-item label='Delay'> <a-form-item label='Delay'>
<a-input v-model.trim="noise.delay"></a-input> <a-input v-model.trim="noise.delay"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='Apply To'>
<a-select v-model="noise.applyTo" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[ s ]]</a-select-option>
</a-select>
</a-form-item>
</a-form> </a-form>
</template> </template>
</template> </template>
@@ -97,7 +105,7 @@
</a-form-item> </a-form-item>
<a-form-item label='non-IP queries'> <a-form-item label='non-IP queries'>
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="s in ['drop','skip']" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' > <a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' >

View File

@@ -167,28 +167,35 @@
<a-col> <a-col>
<a-card size="small" :style="{ padding: '16px' }" hoverable> <a-card size="small" :style="{ padding: '16px' }" hoverable>
<a-row> <a-row>
<a-col :sm="12" :md="6"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`"> <a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
<template #prefix> <template #prefix>
<a-icon type="swap"></a-icon> <a-icon type="swap"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="6"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-icon type="pie-chart"></a-icon> <a-icon type="pie-chart"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="6"> <a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.allTimeTrafficUsage" }}' :value="SizeFormatter.sizeFormat(total.allTime)" :style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix>
<a-icon type="history"></a-icon>
</template>
</a-custom-statistic>
</a-col>
<a-col :sm="12" :md="5">
<a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-icon type="bars"></a-icon> <a-icon type="bars"></a-icon>
</template> </template>
</a-custom-statistic> </a-custom-statistic>
</a-col> </a-col>
<a-col :sm="12" :md="6"> <a-col :sm="12" :md="4">
<a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }"> <a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
<template #prefix> <template #prefix>
<a-space direction="horizontal"> <a-space direction="horizontal">
@@ -484,6 +491,9 @@
</a-tag> </a-tag>
</a-popover> </a-popover>
</template> </template>
<template slot="allTimeInbound" slot-scope="text, dbInbound">
<a-tag>[[ SizeFormatter.sizeFormat(dbInbound.allTime || 0) ]]</a-tag>
</template>
<template slot="enable" slot-scope="text, dbInbound"> <template slot="enable" slot-scope="text, dbInbound">
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch> <a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
</template> </template>
@@ -723,6 +733,11 @@
align: 'center', align: 'center',
width: 60, width: 60,
scopedSlots: { customRender: 'traffic' }, scopedSlots: { customRender: 'traffic' },
}, {
title: '{{ i18n "pages.inbounds.allTimeTraffic" }}',
align: 'center',
width: 60,
scopedSlots: { customRender: 'allTimeInbound' },
}, { }, {
title: '{{ i18n "pages.inbounds.expireDate" }}', title: '{{ i18n "pages.inbounds.expireDate" }}',
align: 'center', align: 'center',
@@ -759,7 +774,10 @@
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
{ title: '{{ i18n "pages.inbounds.createdAt" }}', width: 90, align: 'center', scopedSlots: { customRender: 'createdAt' } },
{ title: '{{ i18n "pages.inbounds.updatedAt" }}', width: 90, align: 'center', scopedSlots: { customRender: 'updatedAt' } },
]; ];
const innerMobileColumns = [ const innerMobileColumns = [
@@ -1075,7 +1093,6 @@
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(), settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(), streamSettings: baseInbound.stream.toString(),
sniffing: baseInbound.sniffing.toString(), sniffing: baseInbound.sniffing.toString(),
allocate: baseInbound.allocate.toString(),
}; };
await this.submit('/panel/inbound/add', data, inModal); await this.submit('/panel/inbound/add', data, inModal);
}, },
@@ -1119,9 +1136,12 @@
protocol: inbound.protocol, protocol: inbound.protocol,
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
data.allocate = inbound.allocate.toString();
await this.submit('/panel/inbound/add', data, inModal); await this.submit('/panel/inbound/add', data, inModal);
}, },
@@ -1139,9 +1159,12 @@
protocol: inbound.protocol, protocol: inbound.protocol,
settings: inbound.settings.toString(), settings: inbound.settings.toString(),
}; };
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString(); if (inbound.canEnableStream()){
data.streamSettings = inbound.stream.toString();
} else if (inbound.stream?.sockopt) {
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
}
data.sniffing = inbound.sniffing.toString(); data.sniffing = inbound.sniffing.toString();
data.allocate = inbound.allocate.toString();
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
}, },
@@ -1409,6 +1432,12 @@
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
return clientStats ? clientStats.up + clientStats.down : 0; return clientStats ? clientStats.up + clientStats.down : 0;
}, },
getAllTimeClient(dbInbound, email) {
if (email.length == 0) return 0;
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
if (!clientStats) return 0;
return clientStats.allTime || (clientStats.up + clientStats.down);
},
getRemStats(dbInbound, email) { getRemStats(dbInbound, email) {
if (email.length == 0) return 0; if (email.length == 0) return 0;
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
@@ -1598,11 +1627,12 @@
}, },
computed: { computed: {
total() { total() {
let down = 0, up = 0; let down = 0, up = 0, allTime = 0;
let clients = 0, deactive = [], depleted = [], expiring = []; let clients = 0, deactive = [], depleted = [], expiring = [];
this.dbInbounds.forEach(dbInbound => { this.dbInbounds.forEach(dbInbound => {
down += dbInbound.down; down += dbInbound.down;
up += dbInbound.up; up += dbInbound.up;
allTime += (dbInbound.allTime || (dbInbound.up + dbInbound.down));
if (this.clientCount[dbInbound.id]) { if (this.clientCount[dbInbound.id]) {
clients += this.clientCount[dbInbound.id].clients; clients += this.clientCount[dbInbound.id].clients;
deactive = deactive.concat(this.clientCount[dbInbound.id].deactive); deactive = deactive.concat(this.clientCount[dbInbound.id].deactive);
@@ -1613,6 +1643,7 @@
return { return {
down: down, down: down,
up: up, up: up,
allTime: allTime,
clients: clients, clients: clients,
deactive: deactive, deactive: deactive,
depleted: depleted, depleted: depleted,

View File

@@ -512,7 +512,7 @@
</a-input-password> </a-input-password>
</a-form-item> </a-form-item>
<a-form-item v-if="twoFactorEnable"> <a-form-item v-if="twoFactorEnable">
<a-input autocomplete="totp" name="twoFactorCode" v-model.trim="user.twoFactorCode" <a-input autocomplete="one-time-code" name="twoFactorCode" v-model.trim="user.twoFactorCode"
placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login"> placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login">
<a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon>
</a-input> </a-input>
@@ -615,4 +615,4 @@
} }
}); });
</script> </script>
{{ template "page/body_end" .}} {{ template "page/body_end" .}}

View File

@@ -207,7 +207,7 @@
settings: { settings: {
domainStrategy: "AsIs", domainStrategy: "AsIs",
noises: [ noises: [
{ type: "rand", packet: "10-20", delay: "10-16" }, { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
], ],
}, },
}, },
@@ -397,7 +397,7 @@
} }
}, },
addNoise() { addNoise() {
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" }; const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
this.noisesArray = [...this.noisesArray, newNoise]; this.noisesArray = [...this.noisesArray, newNoise];
}, },
removeNoise(index) { removeNoise(index) {
@@ -420,6 +420,11 @@
updatedNoises[index] = { ...updatedNoises[index], delay: value }; updatedNoises[index] = { ...updatedNoises[index], delay: value };
this.noisesArray = updatedNoises; this.noisesArray = updatedNoises;
}, },
updateNoiseApplyTo(index, value) {
const updatedNoises = [...this.noisesArray];
updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
this.noisesArray = updatedNoises;
},
}, },
computed: { computed: {
fragment: { fragment: {

View File

@@ -90,6 +90,18 @@
placeholder="10-20"></a-input> placeholder="10-20"></a-input>
</template> </template>
</a-setting-list-item> </a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>ApplyTo</template>
<template #control>
<a-select :value="noise.applyTo" :style="{ width: '100%' }"
:dropdown-class-name="themeSwitcher.currentTheme"
@change="(value) => updateNoiseApplyTo(index, value)">
<a-select-option :value="p" :label="p" v-for="p in ['ip', 'ipv4', 'ipv6']" :key="p">
<span>[[ p ]]</span>
</a-select-option>
</a-select>
</template>
</a-setting-list-item>
<a-space direction="horizontal" :style="{ padding: '10px 20px' }"> <a-space direction="horizontal" :style="{ padding: '10px 20px' }">
<a-button v-if="noisesArray.length > 1" type="danger" <a-button v-if="noisesArray.length > 1" type="danger"
@click="removeNoise(index)">Remove</a-button> @click="removeNoise(index)">Remove</a-button>

View File

@@ -11,7 +11,6 @@ import (
"sort" "sort"
"time" "time"
"slices"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@@ -58,21 +57,21 @@ func (j *CheckClientIpJob) Run() {
func (j *CheckClientIpJob) clearAccessLog() { func (j *CheckClientIpJob) clearAccessLog() {
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
j.checkError(err) j.checkError(err)
defer logAccessP.Close()
accessLogPath, err := xray.GetAccessLogPath() accessLogPath, err := xray.GetAccessLogPath()
j.checkError(err) j.checkError(err)
file, err := os.Open(accessLogPath) file, err := os.Open(accessLogPath)
j.checkError(err) j.checkError(err)
defer file.Close()
_, err = io.Copy(logAccessP, file) _, err = io.Copy(logAccessP, file)
j.checkError(err) j.checkError(err)
logAccessP.Close()
file.Close()
err = os.Truncate(accessLogPath, 0) err = os.Truncate(accessLogPath, 0)
j.checkError(err) j.checkError(err)
j.lastClear = time.Now().Unix() j.lastClear = time.Now().Unix()
} }
@@ -193,10 +192,6 @@ func (j *CheckClientIpJob) checkError(e error) {
} }
} }
func (j *CheckClientIpJob) contains(s []string, str string) bool {
return slices.Contains(s, str)
}
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) { func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
db := database.GetDB() db := database.GetDB()
InboundClientIps := &model.InboundClientIps{} InboundClientIps := &model.InboundClientIps{}

View File

@@ -175,17 +175,42 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
return inbound, false, err return inbound, false, err
} }
// Ensure created_at and updated_at on clients in settings
if len(clients) > 0 {
var settings map[string]any
if err2 := json.Unmarshal([]byte(inbound.Settings), &settings); err2 == nil && settings != nil {
now := time.Now().Unix() * 1000
updatedClients := make([]model.Client, 0, len(clients))
for _, c := range clients {
if c.CreatedAt == 0 {
c.CreatedAt = now
}
c.UpdatedAt = now
updatedClients = append(updatedClients, c)
}
settings["clients"] = updatedClients
if bs, err3 := json.MarshalIndent(settings, "", " "); err3 == nil {
inbound.Settings = string(bs)
} else {
logger.Debug("Unable to marshal inbound settings with timestamps:", err3)
}
} else if err2 != nil {
logger.Debug("Unable to parse inbound settings for timestamps:", err2)
}
}
// Secure client ID // Secure client ID
for _, client := range clients { for _, client := range clients {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
if client.Password == "" { if client.Password == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
if client.Email == "" { if client.Email == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
} else { default:
if client.ID == "" { if client.ID == "" {
return inbound, false, common.NewError("empty client ID") return inbound, false, common.NewError("empty client ID")
} }
@@ -319,6 +344,53 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, false, err return inbound, false, err
} }
// Ensure created_at and updated_at exist in inbound.Settings clients
{
var oldSettings map[string]any
_ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
emailToCreated := map[string]int64{}
if oldSettings != nil {
if oc, ok := oldSettings["clients"].([]any); ok {
for _, it := range oc {
if m, ok2 := it.(map[string]any); ok2 {
if email, ok3 := m["email"].(string); ok3 {
switch v := m["created_at"].(type) {
case float64:
emailToCreated[email] = int64(v)
case int64:
emailToCreated[email] = v
}
}
}
}
}
}
var newSettings map[string]any
if err2 := json.Unmarshal([]byte(inbound.Settings), &newSettings); err2 == nil && newSettings != nil {
now := time.Now().Unix() * 1000
if nSlice, ok := newSettings["clients"].([]any); ok {
for i := range nSlice {
if m, ok2 := nSlice[i].(map[string]any); ok2 {
email, _ := m["email"].(string)
if _, ok3 := m["created_at"]; !ok3 {
if v, ok4 := emailToCreated[email]; ok4 && v > 0 {
m["created_at"] = v
} else {
m["created_at"] = now
}
}
m["updated_at"] = now
nSlice[i] = m
}
}
newSettings["clients"] = nSlice
if bs, err3 := json.MarshalIndent(newSettings, "", " "); err3 == nil {
inbound.Settings = string(bs)
}
}
}
}
oldInbound.Up = inbound.Up oldInbound.Up = inbound.Up
oldInbound.Down = inbound.Down oldInbound.Down = inbound.Down
oldInbound.Total = inbound.Total oldInbound.Total = inbound.Total
@@ -331,7 +403,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing oldInbound.Sniffing = inbound.Sniffing
oldInbound.Allocate = inbound.Allocate
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else { } else {
@@ -421,6 +492,17 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
} }
interfaceClients := settings["clients"].([]any) interfaceClients := settings["clients"].([]any)
// Add timestamps for new clients being appended
nowTs := time.Now().Unix() * 1000
for i := range interfaceClients {
if cm, ok := interfaceClients[i].(map[string]any); ok {
if _, ok2 := cm["created_at"]; !ok2 {
cm["created_at"] = nowTs
}
cm["updated_at"] = nowTs
interfaceClients[i] = cm
}
}
existEmail, err := s.checkEmailsExistForClients(clients) existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil { if err != nil {
return false, err return false, err
@@ -436,15 +518,16 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
// Secure client ID // Secure client ID
for _, client := range clients { for _, client := range clients {
if oldInbound.Protocol == "trojan" { switch oldInbound.Protocol {
case "trojan":
if client.Password == "" { if client.Password == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
} else if oldInbound.Protocol == "shadowsocks" { case "shadowsocks":
if client.Email == "" { if client.Email == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
} else { default:
if client.ID == "" { if client.ID == "" {
return false, common.NewError("empty client ID") return false, common.NewError("empty client ID")
} }
@@ -631,13 +714,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
clientIndex := -1 clientIndex := -1
for index, oldClient := range oldClients { for index, oldClient := range oldClients {
oldClientId := "" oldClientId := ""
if oldInbound.Protocol == "trojan" { switch oldInbound.Protocol {
case "trojan":
oldClientId = oldClient.Password oldClientId = oldClient.Password
newClientId = clients[0].Password newClientId = clients[0].Password
} else if oldInbound.Protocol == "shadowsocks" { case "shadowsocks":
oldClientId = oldClient.Email oldClientId = oldClient.Email
newClientId = clients[0].Email newClientId = clients[0].Email
} else { default:
oldClientId = oldClient.ID oldClientId = oldClient.ID
newClientId = clients[0].ID newClientId = clients[0].ID
} }
@@ -669,6 +753,25 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err return false, err
} }
settingsClients := oldSettings["clients"].([]any) settingsClients := oldSettings["clients"].([]any)
// Preserve created_at and set updated_at for the replacing client
var preservedCreated any
if clientIndex >= 0 && clientIndex < len(settingsClients) {
if oldMap, ok := settingsClients[clientIndex].(map[string]any); ok {
if v, ok2 := oldMap["created_at"]; ok2 {
preservedCreated = v
}
}
}
if len(interfaceClients) > 0 {
if newMap, ok := interfaceClients[0].(map[string]any); ok {
if preservedCreated == nil {
preservedCreated = time.Now().Unix() * 1000
}
newMap["created_at"] = preservedCreated
newMap["updated_at"] = time.Now().Unix() * 1000
interfaceClients[0] = newMap
}
}
settingsClients[clientIndex] = interfaceClients[0] settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients oldSettings["clients"] = settingsClients
@@ -811,8 +914,9 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
if traffic.IsInbound { if traffic.IsInbound {
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag). err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
Updates(map[string]any{ Updates(map[string]any{
"up": gorm.Expr("up + ?", traffic.Up), "up": gorm.Expr("up + ?", traffic.Up),
"down": gorm.Expr("down + ?", traffic.Down), "down": gorm.Expr("down + ?", traffic.Down),
"all_time": gorm.Expr("COALESCE(all_time, 0) + ?", traffic.Up+traffic.Down),
}).Error }).Error
if err != nil { if err != nil {
return err return err
@@ -858,6 +962,7 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email { if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
dbClientTraffics[dbTraffic_index].AllTime += (traffics[traffic_index].Up + traffics[traffic_index].Down)
// Add user in onlineUsers array on traffic // Add user in onlineUsers array on traffic
if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 {
@@ -906,10 +1011,16 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
oldExpiryTime := c["expiryTime"].(float64) oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime) newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime c["expiryTime"] = newExpiryTime
c["updated_at"] = time.Now().Unix() * 1000
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break break
} }
} }
// Backfill created_at and updated_at
if _, ok := c["created_at"]; !ok {
c["created_at"] = time.Now().Unix() * 1000
}
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
settings["clients"] = newClients settings["clients"] = newClients
@@ -1244,11 +1355,12 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@@ -1270,6 +1382,7 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["tgId"] = tgId c["tgId"] = tgId
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@@ -1328,11 +1441,12 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
clientOldEnabled = oldClient.Enable clientOldEnabled = oldClient.Enable
@@ -1355,6 +1469,7 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["enable"] = !clientOldEnabled c["enable"] = !clientOldEnabled
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@@ -1391,11 +1506,12 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@@ -1417,6 +1533,7 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["limitIp"] = count c["limitIp"] = count
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@@ -1448,11 +1565,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@@ -1474,6 +1592,7 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["expiryTime"] = expiry_time c["expiryTime"] = expiry_time
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@@ -1508,11 +1627,12 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
for _, oldClient := range oldClients { for _, oldClient := range oldClients {
if oldClient.Email == clientEmail { if oldClient.Email == clientEmail {
if inbound.Protocol == "trojan" { switch inbound.Protocol {
case "trojan":
clientId = oldClient.Password clientId = oldClient.Password
} else if inbound.Protocol == "shadowsocks" { case "shadowsocks":
clientId = oldClient.Email clientId = oldClient.Email
} else { default:
clientId = oldClient.ID clientId = oldClient.ID
} }
break break
@@ -1534,6 +1654,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
c := clients[client_index].(map[string]any) c := clients[client_index].(map[string]any)
if c["email"] == clientEmail { if c["email"] == clientEmail {
c["totalGB"] = totalGB * 1024 * 1024 * 1024 c["totalGB"] = totalGB * 1024 * 1024 * 1024
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
} }
@@ -1916,6 +2037,25 @@ func (s *InboundService) MigrationRequirements() {
} }
}() }()
// Calculate and backfill all_time from up+down for inbounds and clients
err = tx.Exec(`
UPDATE inbounds
SET all_time = IFNULL(up, 0) + IFNULL(down, 0)
WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0
`).Error
if err != nil {
return
}
err = tx.Exec(`
UPDATE client_traffics
SET all_time = IFNULL(up, 0) + IFNULL(down, 0)
WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0
`).Error
if err != nil {
return
}
// Fix inbounds based problems // Fix inbounds based problems
var inbounds []*model.Inbound var inbounds []*model.Inbound
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
@@ -1954,6 +2094,11 @@ func (s *InboundService) MigrationRequirements() {
c["flow"] = "" c["flow"] = ""
} }
} }
// Backfill created_at and updated_at
if _, ok := c["created_at"]; !ok {
c["created_at"] = time.Now().Unix() * 1000
}
c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c)) newClients = append(newClients, any(c))
} }
settings["clients"] = newClients settings["clients"] = newClients

View File

@@ -235,8 +235,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
} }
// IP fetching with caching // IP fetching with caching
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"} showIp4ServiceLists := []string{
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"} "https://api4.ipify.org",
"https://ipv4.icanhazip.com",
"https://v4.api.ipinfo.io/ip",
"https://ipv4.myexternalip.com/raw",
"https://4.ident.me",
"https://check-host.net/ip",
}
showIp6ServiceLists := []string{
"https://api6.ipify.org",
"https://ipv6.icanhazip.com",
"https://v6.api.ipinfo.io/ip",
"https://ipv6.myexternalip.com/raw",
"https://6.ident.me",
}
if s.cachedIPv4 == "" { if s.cachedIPv4 == "" {
for _, ip4Service := range showIp4ServiceLists { for _, ip4Service := range showIp4ServiceLists {

View File

@@ -40,7 +40,6 @@ var (
isRunning bool isRunning bool
hostname string hostname string
hashStorage *global.HashStorage hashStorage *global.HashStorage
handler *th.Handler
// clients data to adding new client // clients data to adding new client
receiver_inbound_ID int receiver_inbound_ID int
@@ -641,13 +640,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -704,6 +704,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -715,13 +719,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -844,13 +849,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -919,6 +925,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -930,13 +940,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -1035,13 +1046,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 { if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3]) num, err := strconv.Atoi(dataArray[3])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -1101,6 +1113,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId) t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -1112,13 +1128,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 { if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2]) num, err := strconv.Atoi(dataArray[2])
if err == nil { if err == nil {
if num == -2 { switch num {
case -2:
inputNumber = 0 inputNumber = 0
} else if num == -1 { case -1:
if inputNumber > 0 { if inputNumber > 0 {
inputNumber = (inputNumber / 10) inputNumber = (inputNumber / 10)
} }
} else { default:
inputNumber = (inputNumber * 10) + num inputNumber = (inputNumber * 10) + num
} }
} }
@@ -1288,6 +1305,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(callbackQuery.Message.GetChat().ID, message_text) t.addClient(callbackQuery.Message.GetChat().ID, message_text)
} }
@@ -1524,6 +1545,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email)) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_default_ip_limit": case "add_client_default_ip_limit":
@@ -1534,6 +1559,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
if err != nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
return
}
t.addClient(chatId, message_text, messageId) t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email)) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_submit_disable": case "add_client_submit_disable":
@@ -1598,6 +1627,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return return
} }
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails) valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
if err != nil {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
return
}
for _, valid_emails := range valid_emails { for _, valid_emails := range valid_emails {
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails) traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
@@ -1760,6 +1793,10 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
} }
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol) jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
if err != nil {
logger.Warning("BuildJSONForProtocol run failed:", err)
return false, errors.New("failed to build JSON for protocol")
}
newInbound := &model.Inbound{ newInbound := &model.Inbound{
Id: receiver_inbound_ID, Id: receiver_inbound_ID,
@@ -2008,10 +2045,11 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
} }
msg := "" msg := ""
if status == LoginSuccess { switch status {
case LoginSuccess:
msg += t.I18nBot("tgbot.messages.loginSuccess") msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
} else if status == LoginFail { case LoginFail:
msg += t.I18nBot("tgbot.messages.loginFailed") msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password) msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
@@ -2171,6 +2209,22 @@ func (t *Tgbot) clientInfoMsg(
expiryTime = t.I18nBot("tgbot.unlimited") expiryTime = t.I18nBot("tgbot.unlimited")
} else if diff > 172800 || !traffic.Enable { } else if diff > 172800 || !traffic.Enable {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
if diff > 0 {
days := diff / 86400
hours := (diff % 86400) / 3600
minutes := (diff % 3600) / 60
remainingTime := ""
if days > 0 {
remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
}
if hours > 0 {
remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
}
if minutes > 0 {
remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
}
expiryTime += fmt.Sprintf(" (%s)", remainingTime)
}
} else if traffic.ExpiryTime < 0 { } else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
flag = true flag = true

View File

@@ -151,6 +151,8 @@
"getConfigError" = "حدث خطأ أثناء استرجاع ملف الإعدادات" "getConfigError" = "حدث خطأ أثناء استرجاع ملف الإعدادات"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "إجمالي حركة المرور"
"allTimeTrafficUsage" = "إجمالي الاستخدام طوال الوقت"
"title" = "الإدخالات" "title" = "الإدخالات"
"totalDownUp" = "إجمالي المرسل/المستقبل" "totalDownUp" = "إجمالي المرسل/المستقبل"
"totalUsage" = "إجمالي الاستخدام" "totalUsage" = "إجمالي الاستخدام"
@@ -165,6 +167,8 @@
"details" = "تفاصيل" "details" = "تفاصيل"
"transportConfig" = "نقل" "transportConfig" = "نقل"
"expireDate" = "المدة" "expireDate" = "المدة"
"createdAt" = "تاريخ الإنشاء"
"updatedAt" = "تاريخ التحديث"
"resetTraffic" = "إعادة ضبط الترافيك" "resetTraffic" = "إعادة ضبط الترافيك"
"addInbound" = "أضف إدخال" "addInbound" = "أضف إدخال"
"generalActions" = "إجراءات عامة" "generalActions" = "إجراءات عامة"
@@ -561,24 +565,25 @@
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة" "resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
[tgbot] [tgbot]
"keyboardClosed" = "❌ الكيبورد المخصص اتقفلت!" "keyboardClosed" = "❌ لوحة المفاتيح مغلقة!"
"noResult" = "❗ مفيش نتيجة!" "noResult" = "❗ لا يوجد نتائج!"
"noQuery" = "❌ مش لاقي السؤال! استخدم الأمر تاني!" "noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!"
"wentWrong" = "❌ حصل خطأ!" "wentWrong" = "❌ حدث خطأ ما!"
"noIpRecord" = "❗ مفيش سجل IP!" "noIpRecord" = "❗ لا يوجد سجل IP!"
"noInbounds" = "❗ مفيش إدخال متواجد!" "noInbounds" = "❗ لم يتم العثور على أي وارد!"
"unlimited" = "♾ غير محدود (إعادة ضبط)" "unlimited" = "♾ غير محدود (إعادة تعيين)"
"add" = "أضف" "add" = "إضافة"
"month" = "شهر" "month" = "شهر"
"months" = "شهور" "months" = "أشهر"
"day" = "يوم" "day" = "يوم"
"days" = "أيام" "days" = "أيام"
"hours" = "ساعات" "hours" = "ساعات"
"unknown" = "مش معروف" "minutes" = "دقائق"
"inbounds" = "الإدخالات" "unknown" = "غير معروف"
"inbounds" = "الواردات"
"clients" = "العملاء" "clients" = "العملاء"
"offline" = "🔴 أوفلاين" "offline" = "🔴 غير متصل"
"online" = "🟢 أونلاين" "online" = "🟢 متصل"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ أمر مش معروف." "unknown" = "❗ أمر مش معروف."

View File

@@ -151,6 +151,8 @@
"getConfigError" = "An error occurred while retrieving the config file." "getConfigError" = "An error occurred while retrieving the config file."
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "All-time Traffic"
"allTimeTrafficUsage" = "All Time Total Usage"
"title" = "Inbounds" "title" = "Inbounds"
"totalDownUp" = "Total Sent/Received" "totalDownUp" = "Total Sent/Received"
"totalUsage" = "Total Usage" "totalUsage" = "Total Usage"
@@ -165,6 +167,8 @@
"details" = "Details" "details" = "Details"
"transportConfig" = "Transport" "transportConfig" = "Transport"
"expireDate" = "Duration" "expireDate" = "Duration"
"createdAt" = "Created"
"updatedAt" = "Updated"
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound" "addInbound" = "Add Inbound"
"generalActions" = "General Actions" "generalActions" = "General Actions"
@@ -574,6 +578,7 @@
"day" = "Day" "day" = "Day"
"days" = "Days" "days" = "Days"
"hours" = "Hours" "hours" = "Hours"
"minutes" = "Minutes"
"unknown" = "Unknown" "unknown" = "Unknown"
"inbounds" = "Inbounds" "inbounds" = "Inbounds"
"clients" = "Clients" "clients" = "Clients"

View File

@@ -91,7 +91,7 @@
"invalidFormData" = "El formato de los datos de entrada es inválido." "invalidFormData" = "El formato de los datos de entrada es inválido."
"emptyUsername" = "Por favor ingresa el nombre de usuario." "emptyUsername" = "Por favor ingresa el nombre de usuario."
"emptyPassword" = "Por favor ingresa la contraseña." "emptyPassword" = "Por favor ingresa la contraseña."
"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto." "wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
"successLogin" = "Has iniciado sesión en tu cuenta correctamente." "successLogin" = "Has iniciado sesión en tu cuenta correctamente."
[pages.index] [pages.index]
@@ -151,6 +151,8 @@
"getConfigError" = "Ocurrió un error al obtener el archivo de configuración" "getConfigError" = "Ocurrió un error al obtener el archivo de configuración"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Tráfico Total"
"allTimeTrafficUsage" = "Uso total de todos los tiempos"
"title" = "Entradas" "title" = "Entradas"
"totalDownUp" = "Subidas/Descargas Totales" "totalDownUp" = "Subidas/Descargas Totales"
"totalUsage" = "Uso Total" "totalUsage" = "Uso Total"
@@ -165,6 +167,8 @@
"details" = "Detalles" "details" = "Detalles"
"transportConfig" = "Transporte" "transportConfig" = "Transporte"
"expireDate" = "Fecha de Expiración" "expireDate" = "Fecha de Expiración"
"createdAt" = "Creado"
"updatedAt" = "Actualizado"
"resetTraffic" = "Restablecer Tráfico" "resetTraffic" = "Restablecer Tráfico"
"addInbound" = "Agregar Entrada" "addInbound" = "Agregar Entrada"
"generalActions" = "Acciones Generales" "generalActions" = "Acciones Generales"
@@ -535,9 +539,9 @@
[pages.settings.security] [pages.settings.security]
"admin" = "Credenciales de administrador" "admin" = "Credenciales de administrador"
"twoFactor" = "Autenticación de dos factores" "twoFactor" = "Autenticación de dos factores"
"twoFactorEnable" = "Habilitar 2FA" "twoFactorEnable" = "Habilitar 2FA"
"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad." "twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
"twoFactorModalSetTitle" = "Activar autenticación de dos factores" "twoFactorModalSetTitle" = "Activar autenticación de dos factores"
"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores" "twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores"
"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:" "twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:"
@@ -561,23 +565,24 @@
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente" "resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
[tgbot] [tgbot]
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!" "keyboardClosed" = "❌ Teclado cerrado!"
"noResult" = "❗ ¡Sin resultados!" "noResult" = "❗ ¡No hay resultados!"
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!" "noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
"wentWrong" = "❌ ¡Algo salió mal!" "wentWrong" = "❌ ¡Algo salió mal!"
"noIpRecord" = "❗ ¡Sin Registro de IP!" "noIpRecord" = "❗ ¡No hay registro de IP!"
"noInbounds" = "❗ ¡No se encontraron entradas!" "noInbounds" = "❗ ¡No se encontraron entradas!"
"unlimited" = "♾ Ilimitado" "unlimited" = "♾ Ilimitado (Restablecer)"
"add" = "Agregar" "add" = "Añadir"
"month" = "Mes" "month" = "Mes"
"months" = "Meses" "months" = "Meses"
"day" = "Día" "day" = "Día"
"days" = "Días" "days" = "Días"
"hours" = "Horas" "hours" = "Horas"
"minutes" = "Minutos"
"unknown" = "Desconocido" "unknown" = "Desconocido"
"inbounds" = "Entradas" "inbounds" = "Entradas"
"clients" = "Clientes" "clients" = "Clientes"
"offline" = "🔴 Sin conexión" "offline" = "🔴 Desconectado"
"online" = "🟢 En línea" "online" = "🟢 En línea"
[tgbot.commands] [tgbot.commands]

View File

@@ -151,6 +151,8 @@
"getConfigError" = "خطا در دریافت فایل پیکربندی" "getConfigError" = "خطا در دریافت فایل پیکربندی"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "کل ترافیک"
"allTimeTrafficUsage" = "کل استفاده در تمام مدت"
"title" = "کاربران" "title" = "کاربران"
"totalDownUp" = "دریافت/ارسال کل" "totalDownUp" = "دریافت/ارسال کل"
"totalUsage" = "‌‌‌مصرف کل" "totalUsage" = "‌‌‌مصرف کل"
@@ -165,6 +167,8 @@
"details" = "توضیحات" "details" = "توضیحات"
"transportConfig" = "نحوه اتصال" "transportConfig" = "نحوه اتصال"
"expireDate" = "مدت زمان" "expireDate" = "مدت زمان"
"createdAt" = "ایجاد"
"updatedAt" = "به‌روزرسانی"
"resetTraffic" = "ریست ترافیک" "resetTraffic" = "ریست ترافیک"
"addInbound" = "افزودن ورودی" "addInbound" = "افزودن ورودی"
"generalActions" = "عملیات کلی" "generalActions" = "عملیات کلی"
@@ -561,22 +565,23 @@
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی" "resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
[tgbot] [tgbot]
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!" "keyboardClosed" = "❌ صفحه کلید بسته شد!"
"noResult" = "❗ نتیجهای یافت نشد!" "noResult" = "❗ نتیجه ای یافت نشد!"
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!" "noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
"wentWrong" = "❌ مشکلی رخ داده است!" "wentWrong" = "❌ مشکلی پیش آمد!"
"noIpRecord" = "❗ رکورد IP یافت نشد!" "noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
"noInbounds" = "❗ هیچ ورودی یافت نشد!" "noInbounds" = "❗ هیچ ورودی یافت نشد!"
"unlimited" = "♾ - نامحدود(ریست)" "unlimited" = "♾ نامحدود(ریست)"
"add" = "اضافه کردن" "add" = "افزودن"
"month" = "ماه" "month" = "ماه"
"months" = "ماه" "months" = "ماه"
"day" = "روز" "day" = "روز"
"days" = "روز" "days" = "روز"
"hours" = "ساعت" "hours" = "ساعت"
"minutes" = "دقیقه"
"unknown" = "نامشخص" "unknown" = "نامشخص"
"inbounds" = "ورودیها" "inbounds" = "ورودی ها"
"clients" = لاینت‌ها" "clients" = اربران"
"offline" = "🔴 آفلاین" "offline" = "🔴 آفلاین"
"online" = "🟢 آنلاین" "online" = "🟢 آنلاین"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "Terjadi kesalahan saat mengambil file konfigurasi" "getConfigError" = "Terjadi kesalahan saat mengambil file konfigurasi"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Total Lalu Lintas"
"allTimeTrafficUsage" = "Total Penggunaan Sepanjang Waktu"
"title" = "Masuk" "title" = "Masuk"
"totalDownUp" = "Total Terkirim/Diterima" "totalDownUp" = "Total Terkirim/Diterima"
"totalUsage" = "Penggunaan Total" "totalUsage" = "Penggunaan Total"
@@ -165,6 +167,8 @@
"details" = "Rincian" "details" = "Rincian"
"transportConfig" = "Transport" "transportConfig" = "Transport"
"expireDate" = "Durasi" "expireDate" = "Durasi"
"createdAt" = "Dibuat"
"updatedAt" = "Diperbarui"
"resetTraffic" = "Reset Traffic" "resetTraffic" = "Reset Traffic"
"addInbound" = "Tambahkan Masuk" "addInbound" = "Tambahkan Masuk"
"generalActions" = "Tindakan Umum" "generalActions" = "Tindakan Umum"
@@ -561,21 +565,22 @@
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar" "resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Papan ketik kustom ditutup!" "keyboardClosed" = "❌ Keyboard ditutup!"
"noResult" = "❗ Tidak ada hasil!" "noResult" = "❗ Tidak ada hasil!"
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!" "noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
"wentWrong" = "❌ Ada yang salah!" "wentWrong" = "❌ Terjadi kesalahan!"
"noIpRecord" = "❗ Tidak ada Catatan IP!" "noIpRecord" = "❗ Tidak ada Catatan IP!"
"noInbounds" = "❗ Tidak ada masuk ditemukan!" "noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
"unlimited" = "♾ Tak terbatas" "unlimited" = "♾ Tidak terbatas (Reset)"
"add" = "Tambah" "add" = "Tambah"
"month" = "Bulan" "month" = "Bulan"
"months" = "Bulan" "months" = "Bulan"
"day" = "Hari" "day" = "Hari"
"days" = "Hari" "days" = "Hari"
"hours" = "Jam" "hours" = "Jam"
"minutes" = "Menit"
"unknown" = "Tidak diketahui" "unknown" = "Tidak diketahui"
"inbounds" = "Masuk" "inbounds" = "Inbound"
"clients" = "Klien" "clients" = "Klien"
"offline" = "🔴 Offline" "offline" = "🔴 Offline"
"online" = "🟢 Online" "online" = "🟢 Online"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "設定ファイルの取得中にエラーが発生しました" "getConfigError" = "設定ファイルの取得中にエラーが発生しました"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "総トラフィック"
"allTimeTrafficUsage" = "これまでの総使用量"
"title" = "インバウンド一覧" "title" = "インバウンド一覧"
"totalDownUp" = "総アップロード / ダウンロード" "totalDownUp" = "総アップロード / ダウンロード"
"totalUsage" = "総使用量" "totalUsage" = "総使用量"
@@ -165,6 +167,8 @@
"details" = "詳細情報" "details" = "詳細情報"
"transportConfig" = "トランスポート設定" "transportConfig" = "トランスポート設定"
"expireDate" = "有効期限" "expireDate" = "有効期限"
"createdAt" = "作成"
"updatedAt" = "更新"
"resetTraffic" = "トラフィックリセット" "resetTraffic" = "トラフィックリセット"
"addInbound" = "インバウンド追加" "addInbound" = "インバウンド追加"
"generalActions" = "一般操作" "generalActions" = "一般操作"
@@ -561,21 +565,22 @@
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー" "resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
[tgbot] [tgbot]
"keyboardClosed" = "❌ カスタムキーボード閉じられました!" "keyboardClosed" = "❌ キーボード閉じました!"
"noResult" = "❗ 結果がありません!" "noResult" = "❗ 結果がありません!"
"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!" "noQuery" = "❌ クエリが見つかりませんコマンドを再利用してください!"
"wentWrong" = "❌ 問題が発生しました!" "wentWrong" = "❌ 何かがうまくいかなかった!"
"noIpRecord" = "❗ IP記録がありません!" "noIpRecord" = "❗ IPレコードがありません!"
"noInbounds" = "❗ インバウンド接続が見つかりません!" "noInbounds" = "❗ インバウンドが見つかりません!"
"unlimited" = "♾ 無制限" "unlimited" = "♾ 無制限(リセット)"
"add" = "追加" "add" = "追加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "日" "day" = "日"
"days" = "日" "days" = "日"
"hours" = "時間" "hours" = "時間"
"minutes" = "分"
"unknown" = "不明" "unknown" = "不明"
"inbounds" = "インバウンド接続" "inbounds" = "インバウンド"
"clients" = "クライアント" "clients" = "クライアント"
"offline" = "🔴 オフライン" "offline" = "🔴 オフライン"
"online" = "🟢 オンライン" "online" = "🟢 オンライン"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "Ocorreu um erro ao recuperar o arquivo de configuração" "getConfigError" = "Ocorreu um erro ao recuperar o arquivo de configuração"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Tráfego Total"
"allTimeTrafficUsage" = "Uso total de todos os tempos"
"title" = "Inbounds" "title" = "Inbounds"
"totalDownUp" = "Total Enviado/Recebido" "totalDownUp" = "Total Enviado/Recebido"
"totalUsage" = "Uso Total" "totalUsage" = "Uso Total"
@@ -165,6 +167,8 @@
"details" = "Detalhes" "details" = "Detalhes"
"transportConfig" = "Transporte" "transportConfig" = "Transporte"
"expireDate" = "Duração" "expireDate" = "Duração"
"createdAt" = "Criado"
"updatedAt" = "Atualizado"
"resetTraffic" = "Redefinir Tráfego" "resetTraffic" = "Redefinir Tráfego"
"addInbound" = "Adicionar Inbound" "addInbound" = "Adicionar Inbound"
"generalActions" = "Ações Gerais" "generalActions" = "Ações Gerais"
@@ -561,21 +565,22 @@
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída" "resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Teclado personalizado fechado!" "keyboardClosed" = "❌ Teclado fechado!"
"noResult" = "❗ Nenhum resultado!" "noResult" = "❗ Nenhum resultado!"
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!" "noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
"wentWrong" = "❌ Algo deu errado!" "wentWrong" = "❌ Algo deu errado!"
"noIpRecord" = "❗ Nenhum registro de IP!" "noIpRecord" = "❗ Nenhum registro de IP!"
"noInbounds" = "❗ Nenhuma entrada encontrada!" "noInbounds" = "❗ Nenhum inbound encontrado!"
"unlimited" = "♾ Ilimitado (Reiniciar)" "unlimited" = "♾ Ilimitado (Reset)"
"add" = "Adicionar" "add" = "Adicionar"
"month" = "Mês" "month" = "Mês"
"months" = "Meses" "months" = "Meses"
"day" = "Dia" "day" = "Dia"
"days" = "Dias" "days" = "Dias"
"hours" = "Horas" "hours" = "Horas"
"minutes" = "Minutos"
"unknown" = "Desconhecido" "unknown" = "Desconhecido"
"inbounds" = "Entradas" "inbounds" = "Inbounds"
"clients" = "Clientes" "clients" = "Clientes"
"offline" = "🔴 Offline" "offline" = "🔴 Offline"
"online" = "🟢 Online" "online" = "🟢 Online"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "Произошла ошибка при получении конфигурационного файла" "getConfigError" = "Произошла ошибка при получении конфигурационного файла"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Общий трафик"
"allTimeTrafficUsage" = "Общее использование за все время"
"title" = "Инбаунды" "title" = "Инбаунды"
"totalDownUp" = "Объем отправленного/полученного трафика" "totalDownUp" = "Объем отправленного/полученного трафика"
"totalUsage" = "Всего трафика" "totalUsage" = "Всего трафика"
@@ -165,6 +167,8 @@
"details" = "Подробнее" "details" = "Подробнее"
"transportConfig" = "Транспорт" "transportConfig" = "Транспорт"
"expireDate" = "Дата окончания" "expireDate" = "Дата окончания"
"createdAt" = "Создано"
"updatedAt" = "Обновлено"
"resetTraffic" = "Сброс трафика" "resetTraffic" = "Сброс трафика"
"addInbound" = "Создать инбаунд" "addInbound" = "Создать инбаунд"
"generalActions" = "Общие действия" "generalActions" = "Общие действия"
@@ -574,6 +578,7 @@
"day" = "День" "day" = "День"
"days" = "Дней" "days" = "Дней"
"hours" = "Часов" "hours" = "Часов"
"minutes" = "Минуты"
"unknown" = "Неизвестно" "unknown" = "Неизвестно"
"inbounds" = "Инбаунды" "inbounds" = "Инбаунды"
"clients" = "Клиенты" "clients" = "Клиенты"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "Yapılandırma dosyası alınırken bir hata oluştu" "getConfigError" = "Yapılandırma dosyası alınırken bir hata oluştu"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Toplam Trafik"
"allTimeTrafficUsage" = "Tüm Zamanların Toplam Kullanımı"
"title" = "Gelenler" "title" = "Gelenler"
"totalDownUp" = "Toplam Gönderilen/Alınan" "totalDownUp" = "Toplam Gönderilen/Alınan"
"totalUsage" = "Toplam Kullanım" "totalUsage" = "Toplam Kullanım"
@@ -165,6 +167,8 @@
"details" = "Detaylar" "details" = "Detaylar"
"transportConfig" = "Taşıma" "transportConfig" = "Taşıma"
"expireDate" = "Süre" "expireDate" = "Süre"
"createdAt" = "Oluşturuldu"
"updatedAt" = "Güncellendi"
"resetTraffic" = "Trafiği Sıfırla" "resetTraffic" = "Trafiği Sıfırla"
"addInbound" = "Gelen Ekle" "addInbound" = "Gelen Ekle"
"generalActions" = "Genel Eylemler" "generalActions" = "Genel Eylemler"
@@ -561,22 +565,23 @@
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata" "resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Özel klavye kapalı!" "keyboardClosed" = "❌ Klavye kapatıldı!"
"noResult" = "❗ Sonuç yok!" "noResult" = "❗ Sonuç yok!"
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!" "noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
"wentWrong" = "❌ Bir şeyler yanlış gitti!" "wentWrong" = "❌ Bir şeyler yanlış gitti!"
"noIpRecord" = "❗ IP Kaydı yok!" "noIpRecord" = "❗ IP Kaydı Yok!"
"noInbounds" = "❗ Gelen bulunamadı!" "noInbounds" = "❗ Gelen bağlantı bulunamadı!"
"unlimited" = "♾ Sınırsız(Sıfırla)" "unlimited" = "♾ Sınırsız (Sıfırla)"
"add" = "Ekle" "add" = "Ekle"
"month" = "Ay" "month" = "Ay"
"months" = "Aylar" "months" = "Aylar"
"day" = "Gün" "day" = "Gün"
"days" = "Günler" "days" = "Günler"
"hours" = "Saatler" "hours" = "Saatler"
"unknown" = "Bilinmiyor" "minutes" = "Dakika"
"unknown" = "Bilinmeyen"
"inbounds" = "Gelenler" "inbounds" = "Gelenler"
"clients" = "Müşteriler" "clients" = "İstemciler"
"offline" = "🔴 Çevrimdışı" "offline" = "🔴 Çevrimdışı"
"online" = "🟢 Çevrimiçi" "online" = "🟢 Çevrimiçi"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "Виникла помилка під час отримання файлу конфігурації" "getConfigError" = "Виникла помилка під час отримання файлу конфігурації"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Загальний трафік"
"allTimeTrafficUsage" = "Загальне використання за весь час"
"title" = "Вхідні" "title" = "Вхідні"
"totalDownUp" = "Всього надісланих/отриманих" "totalDownUp" = "Всього надісланих/отриманих"
"totalUsage" = "Всього використанно" "totalUsage" = "Всього використанно"
@@ -165,6 +167,8 @@
"details" = "Деталі" "details" = "Деталі"
"transportConfig" = "Транспорт" "transportConfig" = "Транспорт"
"expireDate" = "Тривалість" "expireDate" = "Тривалість"
"createdAt" = "Створено"
"updatedAt" = "Оновлено"
"resetTraffic" = "Скинути трафік" "resetTraffic" = "Скинути трафік"
"addInbound" = "Додати вхідний" "addInbound" = "Додати вхідний"
"generalActions" = "Загальні дії" "generalActions" = "Загальні дії"
@@ -561,19 +565,20 @@
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку" "resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Спеціальна клавіатура закрита!" "keyboardClosed" = "❌ Клавіатуру закрито!"
"noResult" = "❗ Немає результату!" "noResult" = "❗ Немає результату!"
"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!" "noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!"
"wentWrong" = "❌ Щось пішло не так!" "wentWrong" = "❌ Щось пішло не так!"
"noIpRecord" = "❗ Немає IP-запису!" "noIpRecord" = "❗ Немає запису IP!"
"noInbounds" = "❗ Вхідних не знайдено!" "noInbounds" = "❗ Вхідні не знайдені!"
"unlimited" = "♾ Необмежений (скинути)" "unlimited" = "♾ Необмежено (Скинути)"
"add" = "Додати" "add" = "Додати"
"month" = "Місяць" "month" = "Місяць"
"months" = "Місяці" "months" = "Місяці"
"day" = "День" "day" = "День"
"days" = "Дні" "days" = "Дні"
"hours" = "Годинник" "hours" = "Години"
"minutes" = "Хвилини"
"unknown" = "Невідомо" "unknown" = "Невідомо"
"inbounds" = "Вхідні" "inbounds" = "Вхідні"
"clients" = "Клієнти" "clients" = "Клієнти"

View File

@@ -91,7 +91,7 @@
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ." "invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
"emptyUsername" = "Vui lòng nhập tên người dùng." "emptyUsername" = "Vui lòng nhập tên người dùng."
"emptyPassword" = "Vui lòng nhập mật khẩu." "emptyPassword" = "Vui lòng nhập mật khẩu."
"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ." "wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
"successLogin" = "Bạn đã đăng nhập vào tài khoản thành công." "successLogin" = "Bạn đã đăng nhập vào tài khoản thành công."
[pages.index] [pages.index]
@@ -151,6 +151,8 @@
"getConfigError" = "Lỗi xảy ra khi truy xuất tệp cấu hình" "getConfigError" = "Lỗi xảy ra khi truy xuất tệp cấu hình"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "Tổng Lưu Lượng"
"allTimeTrafficUsage" = "Tổng mức sử dụng mọi lúc"
"title" = "Điểm vào (Inbounds)" "title" = "Điểm vào (Inbounds)"
"totalDownUp" = "Tổng tải lên/tải xuống" "totalDownUp" = "Tổng tải lên/tải xuống"
"totalUsage" = "Tổng sử dụng" "totalUsage" = "Tổng sử dụng"
@@ -165,6 +167,8 @@
"details" = "Chi tiết" "details" = "Chi tiết"
"transportConfig" = "Giao vận" "transportConfig" = "Giao vận"
"expireDate" = "Ngày hết hạn" "expireDate" = "Ngày hết hạn"
"createdAt" = "Tạo lúc"
"updatedAt" = "Cập nhật"
"resetTraffic" = "Đặt lại lưu lượng" "resetTraffic" = "Đặt lại lưu lượng"
"addInbound" = "Thêm điểm vào" "addInbound" = "Thêm điểm vào"
"generalActions" = "Hành động chung" "generalActions" = "Hành động chung"
@@ -535,9 +539,9 @@
[pages.settings.security] [pages.settings.security]
"admin" = "Thông tin đăng nhập quản trị viên" "admin" = "Thông tin đăng nhập quản trị viên"
"twoFactor" = "Xác thực hai yếu tố" "twoFactor" = "Xác thực hai yếu tố"
"twoFactorEnable" = "Bật 2FA" "twoFactorEnable" = "Bật 2FA"
"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn." "twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố" "twoFactorModalSetTitle" = "Bật xác thực hai yếu tố"
"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố" "twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố"
"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:" "twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:"
@@ -561,22 +565,23 @@
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi" "resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
[tgbot] [tgbot]
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!" "keyboardClosed" = "❌ Bàn phím đã đóng!"
"noResult" = "❗ Không có kết quả!" "noResult" = "❗ Không có kết quả!"
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!" "noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!"
"wentWrong" = "❌ Đã xảy ra lỗi!" "wentWrong" = "❌ Đã xảy ra lỗi!"
"noIpRecord" = "❗ Không có bản ghi IP!" "noIpRecord" = "❗ Không có bản ghi IP!"
"noInbounds" = "❗ Không tìm thấy inbound!" "noInbounds" = "❗ Không tìm thấy inbound!"
"unlimited" = "♾ Không giới hạn" "unlimited" = "♾ Không giới hạn (Đặt lại)"
"add" = "Thêm" "add" = "Thêm"
"month" = "Tháng" "month" = "Tháng"
"months" = "Tháng" "months" = "Tháng"
"day" = "Ngày" "day" = "Ngày"
"days" = "Ngày" "days" = "Ngày"
"hours" = "Giờ" "hours" = "Giờ"
"unknown" = "Không rõ" "minutes" = "Phút"
"inbounds" = "Vào" "unknown" = "Không xác định"
"clients" = "Các người dùng" "inbounds" = "Inbound"
"clients" = "Client"
"offline" = "🔴 Ngoại tuyến" "offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến" "online" = "🟢 Trực tuyến"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "检索配置文件时出错" "getConfigError" = "检索配置文件时出错"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "累计总流量"
"allTimeTrafficUsage" = "所有时间总使用量"
"title" = "入站列表" "title" = "入站列表"
"totalDownUp" = "总上传 / 下载" "totalDownUp" = "总上传 / 下载"
"totalUsage" = "总用量" "totalUsage" = "总用量"
@@ -165,6 +167,8 @@
"details" = "详细信息" "details" = "详细信息"
"transportConfig" = "传输配置" "transportConfig" = "传输配置"
"expireDate" = "到期时间" "expireDate" = "到期时间"
"createdAt" = "创建时间"
"updatedAt" = "更新时间"
"resetTraffic" = "重置流量" "resetTraffic" = "重置流量"
"addInbound" = "添加入站" "addInbound" = "添加入站"
"generalActions" = "通用操作" "generalActions" = "通用操作"
@@ -563,19 +567,20 @@
[tgbot] [tgbot]
"keyboardClosed" = "❌ 自定义键盘已关闭!" "keyboardClosed" = "❌ 自定义键盘已关闭!"
"noResult" = "❗ 没有结果!" "noResult" = "❗ 没有结果!"
"noQuery" = "❌ 未找到查询!请重新使用命令!" "noQuery" = "❌ 未找到查询!请再次使用命令!"
"wentWrong" = "❌ 出了点问题!" "wentWrong" = "❌ 出了点问题!"
"noIpRecord" = "❗ 没有 IP 记录!" "noIpRecord" = "❗ 没有IP记录"
"noInbounds" = "❗ 没有找到入站连接" "noInbounds" = "❗ 找到入站!"
"unlimited" = "♾ 无限" "unlimited" = "♾ 无限(重置)"
"add" = "添加" "add" = "添加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "天" "day" = "天"
"days" = "天" "days" = "天"
"hours" = "小时" "hours" = "小时"
"minutes" = "分钟"
"unknown" = "未知" "unknown" = "未知"
"inbounds" = "入站连接" "inbounds" = "入站"
"clients" = "客户端" "clients" = "客户端"
"offline" = "🔴 离线" "offline" = "🔴 离线"
"online" = "🟢 在线" "online" = "🟢 在线"

View File

@@ -151,6 +151,8 @@
"getConfigError" = "檢索設定檔時發生錯誤" "getConfigError" = "檢索設定檔時發生錯誤"
[pages.inbounds] [pages.inbounds]
"allTimeTraffic" = "累計總流量"
"allTimeTrafficUsage" = "所有时间总使用量"
"title" = "入站列表" "title" = "入站列表"
"totalDownUp" = "總上傳 / 下載" "totalDownUp" = "總上傳 / 下載"
"totalUsage" = "總用量" "totalUsage" = "總用量"
@@ -165,6 +167,8 @@
"details" = "詳細資訊" "details" = "詳細資訊"
"transportConfig" = "傳輸配置" "transportConfig" = "傳輸配置"
"expireDate" = "到期時間" "expireDate" = "到期時間"
"createdAt" = "建立時間"
"updatedAt" = "更新時間"
"resetTraffic" = "重置流量" "resetTraffic" = "重置流量"
"addInbound" = "新增入站" "addInbound" = "新增入站"
"generalActions" = "通用操作" "generalActions" = "通用操作"
@@ -563,22 +567,23 @@
[tgbot] [tgbot]
"keyboardClosed" = "❌ 自定義鍵盤已關閉!" "keyboardClosed" = "❌ 自定義鍵盤已關閉!"
"noResult" = "❗ 沒有結果!" "noResult" = "❗ 沒有結果!"
"noQuery" = "❌ 未找到查詢!請重新使用命令!" "noQuery" = "❌ 未找到查詢!請再次使用命令!"
"wentWrong" = "❌ 出了點問題!" "wentWrong" = "❌ 出了點問題!"
"noIpRecord" = "❗ 沒有 IP 記錄!" "noIpRecord" = "❗ 沒有IP記錄"
"noInbounds" = "❗ 沒有找到入站連線" "noInbounds" = "❗ 找到入站!"
"unlimited" = "♾ 無限" "unlimited" = "♾ 無限(重置)"
"add" = "新增" "add" = "添加"
"month" = "月" "month" = "月"
"months" = "月" "months" = "月"
"day" = "天" "day" = "天"
"days" = "天" "days" = "天"
"hours" = "小時" "hours" = "小時"
"minutes" = "分鐘"
"unknown" = "未知" "unknown" = "未知"
"inbounds" = "入站連線" "inbounds" = "入站"
"clients" = "客戶端" "clients" = "客戶端"
"offline" = "🔴 離線" "offline" = "🔴 離線"
"online" = "🟢 線" "online" = "🟢 線"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ 未知命令" "unknown" = "❗ 未知命令"

249
x-ui.sh
View File

@@ -398,37 +398,6 @@ show_log() {
esac esac
} }
show_banlog() {
local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
fi
if [[ -f "$system_log" ]]; then
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
echo ""
fi
if [[ -f "${iplimit_banned_log_path}" ]]; then
echo -e "${green}3X-IPL ban log entries:${plain}"
if [[ -s "${iplimit_banned_log_path}" ]]; then
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
else
echo -e "${yellow}Ban log file is empty${plain}"
fi
else
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
fi
echo -e "\n${green}Current jail status:${plain}"
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
}
bbr_menu() { bbr_menu() {
echo -e "${green}\t1.${plain} Enable BBR" echo -e "${green}\t1.${plain} Enable BBR"
echo -e "${green}\t2.${plain} Disable BBR" echo -e "${green}\t2.${plain} Disable BBR"
@@ -1005,7 +974,7 @@ ssl_cert_issue() {
# install socat second # install socat second
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt update && apt install socat -y apt-get update && apt-get install socat -y
;; ;;
centos | rhel | almalinux | rocky | ol) centos | rhel | almalinux | rocky | ol)
yum -y update && yum -y install socat yum -y update && yum -y install socat
@@ -1330,81 +1299,7 @@ run_speedtest() {
speedtest speedtest
} }
create_iplimit_jails() {
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
# On Debian 12+ fail2ban's default backend should be changed to systemd
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
backend=auto
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
maxretry=2
findtime=32
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-allports.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
protocol = tcp
chain = INPUT
EOF
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
}
iplimit_remove_conflicts() {
local jail_files=(
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in "${jail_files[@]}"; do
# Check for [3x-ipl] config in jail file then remove it
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
fi
done
}
ip_validation() { ip_validation() {
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
@@ -1514,14 +1409,22 @@ install_iplimit() {
# Check the OS and install necessary packages # Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu) ubuntu)
apt-get update
if [[ "${os_version}" -ge 24 ]]; then if [[ "${os_version}" -ge 24 ]]; then
apt update && apt install python3-pip -y apt-get install python3-pip -y
python3 -m pip install pyasynchat --break-system-packages python3 -m pip install pyasynchat --break-system-packages
fi fi
apt update && apt install fail2ban -y apt-get install fail2ban -y
;; ;;
debian | armbian) debian)
apt update && apt install fail2ban -y apt-get update
if [ "$os_version" -ge 12 ]; then
apt-get install -y python3-systemd
fi
apt-get install -y fail2ban
;;
armbian)
apt-get update && apt-get install fail2ban -y
;; ;;
centos | rhel | almalinux | rocky | ol) centos | rhel | almalinux | rocky | ol)
yum update -y && yum install epel-release -y yum update -y && yum install epel-release -y
@@ -1632,11 +1535,129 @@ remove_iplimit() {
esac esac
} }
SSH_port_forwarding() { show_banlog() {
local server_ip=$(curl -s --max-time 3 https://api.ipify.org) local system_log="/var/log/fail2ban.log"
if [ -z "$server_ip" ]; then
server_ip=$(curl -s --max-time 3 https://4.ident.me) echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1
fi fi
if [[ -f "$system_log" ]]; then
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
echo ""
fi
if [[ -f "${iplimit_banned_log_path}" ]]; then
echo -e "${green}3X-IPL ban log entries:${plain}"
if [[ -s "${iplimit_banned_log_path}" ]]; then
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
else
echo -e "${yellow}Ban log file is empty${plain}"
fi
else
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
fi
echo -e "\n${green}Current jail status:${plain}"
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
}
create_iplimit_jails() {
# Use default bantime if not passed => 30 minutes
local bantime="${1:-30}"
# Uncomment 'allowipv6 = auto' in fail2ban.conf
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
# On Debian 12+ fail2ban's default backend should be changed to systemd
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
backend=auto
filter=3x-ipl
action=3x-ipl
logpath=${iplimit_log_path}
maxretry=2
findtime=32
bantime=${bantime}m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[INCLUDES]
before = iptables-allports.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
[Init]
name = default
protocol = tcp
chain = INPUT
EOF
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
}
iplimit_remove_conflicts() {
local jail_files=(
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in "${jail_files[@]}"; do
# Check for [3x-ipl] config in jail file then remove it
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
fi
done
}
SSH_port_forwarding() {
local URL_lists=(
"https://api4.ipify.org"
"https://ipv4.icanhazip.com"
"https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip=""
for ip_address in "${URL_lists[@]}"; do
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
if [[ -n "${server_ip}" ]]; then
break
fi
done
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}') local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}') local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}') local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')

View File

@@ -7,6 +7,7 @@ type ClientTraffic struct {
Email string `json:"email" form:"email" gorm:"unique"` Email string `json:"email" form:"email" gorm:"unique"`
Up int64 `json:"up" form:"up"` Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"` Down int64 `json:"down" form:"down"`
AllTime int64 `json:"allTime" form:"allTime"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Total int64 `json:"total" form:"total"` Total int64 `json:"total" form:"total"`
Reset int `json:"reset" form:"reset" gorm:"default:0"` Reset int `json:"reset" form:"reset" gorm:"default:0"`

View File

@@ -14,7 +14,6 @@ type InboundConfig struct {
StreamSettings json_util.RawMessage `json:"streamSettings"` StreamSettings json_util.RawMessage `json:"streamSettings"`
Tag string `json:"tag"` Tag string `json:"tag"`
Sniffing json_util.RawMessage `json:"sniffing"` Sniffing json_util.RawMessage `json:"sniffing"`
Allocate json_util.RawMessage `json:"allocate"`
} }
func (c *InboundConfig) Equals(other *InboundConfig) bool { func (c *InboundConfig) Equals(other *InboundConfig) bool {
@@ -39,8 +38,5 @@ func (c *InboundConfig) Equals(other *InboundConfig) bool {
if !bytes.Equal(c.Sniffing, other.Sniffing) { if !bytes.Equal(c.Sniffing, other.Sniffing) {
return false return false
} }
if !bytes.Equal(c.Allocate, other.Allocate) {
return false
}
return true return true
} }